diff options
| author | Dylan Araps <dylan.araps@gmail.com> | 2026-02-27 13:41:56 +0200 |
|---|---|---|
| committer | Dylan Araps <dylan.araps@gmail.com> | 2026-02-27 13:41:56 +0200 |
| commit | da28548905dab7c60c3fcec4975ccfa23e315909 (patch) | |
| tree | 9cef34a718563969a6bcda5cfeff033eeaf6b1a0 /lib | |
0.99.0
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/arg.h | 77 | ||||
| -rw-r--r-- | lib/bitset.h | 152 | ||||
| -rw-r--r-- | lib/date.h | 139 | ||||
| -rw-r--r-- | lib/readline.h | 529 | ||||
| -rw-r--r-- | lib/str.h | 183 | ||||
| -rw-r--r-- | lib/term.h | 206 | ||||
| -rw-r--r-- | lib/term_key.h | 292 | ||||
| -rw-r--r-- | lib/utf8.h | 185 | ||||
| -rw-r--r-- | lib/util.h | 345 | ||||
| -rw-r--r-- | lib/vt.h | 177 |
10 files changed, 2285 insertions, 0 deletions
diff --git a/lib/arg.h b/lib/arg.h new file mode 100644 index 0000000..06d3024 --- /dev/null +++ b/lib/arg.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2026 Dylan Araps + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef DYLAN_ARG +#define DYLAN_ARG + +struct argv { + const char *const *argv; + int c; +}; + +struct arg { + const char *pos; + int sign; + int name; +}; + +static inline struct argv +arg_init(int argc, char *a[]) +{ + (void) argc; + return (struct argv) { .argv = (const char *const *) ++a }; +} + +static inline struct arg +arg_next(struct argv *s) +{ + static const signed char t[256] = { + ['-'] = '-' - 1, ['+'] = '+' - 1, + }; + + struct arg a = {0}; + a.pos = *s->argv; + a.sign = a.pos ? t[(unsigned char) **s->argv] + 1 : -1; + + if (a.sign > 1) { + s->c |= !s->c; + a.name = (unsigned char) (a.pos[s->c] ? a.pos[s->c] : a.sign); + s->c += !!a.pos[s->c]; + s->c *= !!a.pos[s->c]; + } + + s->argv += !s->c; + return a; +} + +static inline const char * +arg_next_positional(struct argv *s) +{ + const char *a = *s->argv; + if (!a) return a; + a += s->c; + s->c = 0; + s->argv++; + return a; +} + +#endif // DYLAN_ARG + diff --git a/lib/bitset.h b/lib/bitset.h new file mode 100644 index 0000000..d8979de --- /dev/null +++ b/lib/bitset.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2026 Dylan Araps + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef DYLAN_BITSET_H +#define DYLAN_BITSET_H + +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +#include "util.h" + +enum { + BITSET_WORD_BITS = 64, + BITSET_WORD_SHIFT = 6, + BITSET_WORD_MASK = BITSET_WORD_BITS - 1, +}; + +#define BITSET_W(n) (((n) + BITSET_WORD_MASK) >> BITSET_WORD_SHIFT) + +static inline u8 +bitset_get(const u64 *b, usize i) +{ + return (b[i >> BITSET_WORD_SHIFT] >> (i & BITSET_WORD_MASK)) & 1ull; +} + +static inline void +bitset_set(u64 *b, usize i) +{ + b[i >> BITSET_WORD_SHIFT] |= 1ull << (i & BITSET_WORD_MASK); +} + +static inline void +bitset_clr(u64 *b, usize i) +{ + b[i >> BITSET_WORD_SHIFT] &= ~(1ull << (i & BITSET_WORD_MASK)); +} + +static inline void +bitset_tog(u64 *b, usize i) +{ + b[i >> BITSET_WORD_SHIFT] ^= 1ull << (i & BITSET_WORD_MASK); +} + +static inline void +bitset_assign(u64 *b, usize i, int v) +{ + u64 *w = &b[i >> BITSET_WORD_SHIFT]; + u64 m = 1ull << (i & BITSET_WORD_MASK); + *w = v ? (*w | m) : (*w & ~m); +} + +static inline usize +bitset_count(const u64 *b, usize l) +{ + usize c = 0; + for (usize i = 0, w = BITSET_W(l); i < w; i++) + c += u64_popcount(b[i]); + return c; +} + +static inline void +bitset_set_all(u64 *v, usize n) +{ + memset(v, 0xff, BITSET_W(n) * sizeof *v); +} + +static inline void +bitset_clr_all(u64 *v, usize n) +{ + memset(v, 0, BITSET_W(n) * sizeof *v); +} + +static inline void +bitset_invert(u64 *v, usize n) +{ + usize w = BITSET_W(n); + for (usize i = 0; i < w; i++) v[i] = ~v[i]; + usize r = n & BITSET_WORD_MASK; + if (r) v[w - 1] &= (1ull << r) - 1; +} + +static inline void +bitset_swap(u64 *b, usize i, usize j) +{ + u8 bi = bitset_get(b, i); + u8 bj = bitset_get(b, j); + bitset_assign(b, i, bj); + bitset_assign(b, j, bi); +} + +static inline usize +bitset_next_set(const u64 *b, usize i, usize n) +{ + if (i >= n) return SIZE_MAX; + usize wi = i >> BITSET_WORD_SHIFT; + usize wN = BITSET_W(n); + u64 w = b[wi]; + w &= (~0ull << (i & BITSET_WORD_MASK)); + + for (;;) { + if (w) { + usize j = (wi << BITSET_WORD_SHIFT) + u64_ctz(w); + return j < n ? j : SIZE_MAX; + } + + if (++wi >= wN) break; + w = b[wi]; + } + + return SIZE_MAX; +} + +static inline usize +bitset_prev_set(const u64 *b, usize i, usize n) +{ + if (i >= n) i = n - 1; + usize wi = i >> BITSET_WORD_SHIFT; + u64 w = b[wi]; + u64 r = (u64)(i & BITSET_WORD_MASK); + w &= (r == BITSET_WORD_MASK) ? ~0ull : ((1ull << (r + 1)) - 1ull); + + for (;;) { + if (w) + return (wi << BITSET_WORD_SHIFT) + (BITSET_WORD_MASK - (usize)u64_clz(w)); + if (!wi) break; + w = b[--wi]; + } + + return SIZE_MAX; +} + +#endif // DYLAN_BITSET_H + diff --git a/lib/date.h b/lib/date.h new file mode 100644 index 0000000..6d8b060 --- /dev/null +++ b/lib/date.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2026 Dylan Araps + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef DYLAN_DATE_H +#define DYLAN_DATE_H + +// +// Fast date algorithm, C implementation. +// Source: https://www.benjoffe.com/fast-date-64 +// +#include <stddef.h> +#include <stdint.h> + +#include "util.h" + +#define C1 505054698555331ull // floor(2^64 * 4 / 146097) +#define C2 50504432782230121ull // ceil(2^64 * 4 / 1461) +#define C3 8619973866219416ull // floor(2^64 / 2140) + +#define SCALE 32u +#define SHIFT0 (30556u * SCALE) +#define SHIFT1 (5980u * SCALE) + +#if defined(__SIZEOF_INT128__) +#define ERAS 4726498270ull +#define D_SHIFT ((u64)(146097ull * ERAS - 719469ull)) +#define Y_SHIFT ((u64)(400ull * ERAS - 1ull)) + +static inline u64 +mulhi(u64 a, u64 b) +{ + __uint128_t p = (__uint128_t)a * (__uint128_t)b; + return (u64)(p >> 64); +} + +static inline void +mul128(u64 a, u64 b, u64 *hi, u64 *lo) +{ + __uint128_t p = (__uint128_t)a * (__uint128_t)b; + *hi = (u64)(p >> 64); + *lo = (u64)p; +} + +#else + +#define ERAS 14704u +#define D_SHIFT ((u64)(146097ull * (u64)ERAS - 719469ull)) +#define Y_SHIFT ((u64)(400ull * (u64)ERAS - 1ull)) + +static inline u64 +mulhi(u64 a, u64 b) +{ + u64 a0 = (u32)a; + u64 a1 = a >> 32; + u64 b0 = (u32)b; + u64 b1 = b >> 32; + u64 p00 = a0 * b0; + u64 p01 = a0 * b1; + u64 p10 = a1 * b0; + u64 p11 = a1 * b1; + u64 mid = (p00 >> 32) + (u32)p01 + (u32)p10; + return p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32); +} + +static inline void +mul128(u64 a, u64 b, u64 *hi, u64 *lo) +{ + u64 a0 = (u32)a; + u64 a1 = a >> 32; + u64 b0 = (u32)b; + u64 b1 = b >> 32; + u64 p00 = a0 * b0; + u64 p01 = a0 * b1; + u64 p10 = a1 * b0; + u64 p11 = a1 * b1; + u64 mid = (p00 >> 32) + (u32)p01 + (u32)p10; + *hi = p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32); + *lo = (mid << 32) | (u32)p00; +} + +#endif // defined(__SIZEOF_INT128__) + +static inline void +ut_to_date(s32 day, s32 *Y, u32 *M, u32 *D) +{ + u64 rev = (u64)D_SHIFT - (u64)(s64)day; // Reverse day count. + u64 cen = mulhi(C1, rev); // Divide 36524.25 + u64 jul = rev - cen / 4u + cen; // Julian map. + u64 num_hi, num_lo; + mul128(C2, jul, &num_hi, &num_lo); // Divide 365.25 + u64 yrs64 = (u64)Y_SHIFT - (u64)num_hi; // Forward year. + u64 yrs = (u32)yrs64; + u32 ypt = (u32)mulhi((u64)(24451u * SCALE), num_lo); // Year (backwards). + u32 bump = (ypt < (3952u * SCALE)); // Jan or Feb. + u32 shift = bump ? SHIFT1 : SHIFT0; // Month offset. + u32 N = (u32)((yrs % 4u) * (16u * SCALE) + shift - ypt); // Leap years. + u32 m = N / (2048u * SCALE); + u32 d = (u32)mulhi(C3, (u64)(N % (2048u * SCALE))); // Divide 2140 + *Y = (s32)yrs + (s32)bump; + *M = m; + *D = d + 1u; +} + +static inline void +ut_to_date_time(s64 tz, s32 day, s32 *Y, u32 *M, u32 *D, u32 *h, u32 *m, u32 *s) +{ + s64 us = tz + day; + s32 days = (s32)(us / 86400); + s32 r = (s32)(us % 86400); + if (r < 0) { r += 86400; days -= 1; } + ut_to_date(days, Y, M, D); + u32 hr = r / 3600; + r -= hr * 3600; + *h = hr; + u32 mn = r / 60; + *m = mn; + *s = r - mn * 60; +} + +#endif // DYLAN_DATE_H + diff --git a/lib/readline.h b/lib/readline.h new file mode 100644 index 0000000..2c05341 --- /dev/null +++ b/lib/readline.h @@ -0,0 +1,529 @@ +/* + * Copyright (c) 2026 Dylan Araps + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef DYLAN_READLINE +#define DYLAN_READLINE + +#ifndef RL_MAX +#error "RL_MAX not set" +#endif + +#include <assert.h> +#include <string.h> + +#include "str.h" +#include "utf8.h" +#include "util.h" + +enum { + RL_NONE, + RL_PARTIAL, + RL_FULL, + RL_CAP = (RL_MAX >> 1) - 3, +}; + +struct readline { + str cl; + str cr; + cut pr; + + usize vx; + usize vw; + + usize prw; + usize clw; + usize crw; +}; + +static inline int +rl_is_ifs(int c) +{ + return c == ' ' || c == '\t'; +} + +static inline usize +rl_prompt(const struct readline *r) +{ + return r->prw + !!r->prw; +} + +static inline usize +rl_cursor(const struct readline *r) +{ + return rl_prompt(r) + r->clw; +} + +static inline usize +rl_total(const struct readline *r) +{ + return rl_cursor(r) + r->crw; +} + +static inline void +rl_vw_set(struct readline *r, usize vw) +{ + assert(vw); + r->vw = vw; + if (r->vx >= vw) r->vx = vw - 1; +} + +static inline void +rl_pr_set(struct readline *r, cut pr) +{ + r->pr = pr; + usize lw; + r->prw = utf8_cols(pr.d, pr.l, &lw); +} + +static inline void +rl_cr_set(struct readline *r, cut c) +{ + assert(c.l <= RL_CAP); + memcpy(r->cr.m + (r->cr.c - c.l), c.d, c.l); + r->cr.l = c.l; + usize lw; + r->crw = utf8_cols(c.d, c.l, &lw); +} + +static inline void +rl_cl_sync(struct readline *r) +{ + usize lw; + r->clw = utf8_cols(r->cl.m, r->cl.l, &lw); + usize c = rl_cursor(r); + if (c < r->vw) r->vx = c; + else r->vx = r->vw - lw; +} + +static inline void +rl_init(struct readline *r, usize vw, cut pr) +{ + STR_INIT(&r->cl, RL_MAX, 0, 0); + STR_INIT(&r->cr, RL_MAX >> 1, 0, 0); + rl_vw_set(r, vw); + rl_pr_set(r, pr); + r->vx = rl_prompt(r); + r->clw = 0; + r->crw = 0; +} + +static inline const char * +rl_cr_ptr(const struct readline *r) +{ + return r->cr.m + (r->cr.c - r->cr.l); +} + +static inline cut +rl_cr_get(const struct readline *r) +{ + return (cut) { rl_cr_ptr(r), r->cr.l }; +} + +static inline cut +rl_cl_get(const struct readline *r) +{ + return (cut) { r->cl.m, r->cl.l }; +} + +static inline usize +rl_cl_last(const struct readline *r, u32 *cp, int *w) +{ + usize l = utf8_decode_rev((const unsigned char *)r->cl.m, r->cl.l, cp); + *w = utf8_width(*cp); + return l; +} + +static inline usize +rl_cr_first(const struct readline *r, u32 *cp, int *w) +{ + const char *p = rl_cr_ptr(r); + char *n = utf8_decode((void *)p, cp); + *w = utf8_width(*cp); + return (usize)(n - p); +} + +static inline usize +rl_offset(const struct readline *r) +{ + usize c = rl_cursor(r); + return c > r->vx ? c - r->vx : 0; +} + +static inline int +rl_empty(const struct readline *r) +{ + return !r->cl.l && !r->cr.l; +} + +static inline void +rl_clear(struct readline *r) +{ + rl_pr_set(r, CUT_NULL); + r->cl.l = 0; + r->cr.l = 0; + r->clw = 0; + r->crw = 0; + r->vx = 0; +} + +static inline usize +rl_consume_cl(struct readline *r) +{ + usize w = 0; + do { + u32 cp; + int cw; + usize l = rl_cl_last(r, &cp, &cw); + r->cl.l -= l; + r->clw -= cw; + w += (usize)cw; + if (cw != 0) break; + } while (r->cl.l); + return w; +} + +static inline usize +rl_consume_cr(struct readline *r) +{ + usize w = 0; + do { + u32 cp; + int cw; + usize l = rl_cr_first(r, &cp, &cw); + r->cr.l -= l; + r->crw -= cw; + w += (usize)cw; + if (cw != 0 && !r->cr.l) break; + if (cw != 0) { + u32 ncp; + int nw; + rl_cr_first(r, &ncp, &nw); + if (nw != 0) break; + } + } while (r->cr.l); + return w; +} + +static inline int +rl_take_left_to_right(struct readline *r, usize *wo) +{ + if (!r->cl.l) return 0; + usize tw = 0; + for (;;) { + u32 cp; + int w; + usize l = rl_cl_last(r, &cp, &w); + if (r->cr.l + l > RL_CAP) + return -1; + r->cl.l -= l; + r->clw -= w; + usize o = r->cr.c - r->cr.l - l; + memcpy(r->cr.m + o, r->cl.m + r->cl.l, l); + r->cr.l += l; + r->crw += w; + tw += (usize)w; + if (!r->cl.l || w != 0) break; + } + if (wo) *wo = tw; + return 1; +} + +static inline int +rl_take_right_to_left(struct readline *r, usize *wo) +{ + if (!r->cr.l) return 0; + usize tw = 0; + for (;;) { + u32 cp; + int w; + usize l = rl_cr_first(r, &cp, &w); + if (r->cl.l + l > RL_CAP) + return -1; + str_copy(&r->cl, rl_cr_ptr(r), l); + r->cr.l -= l; + r->clw += w; + r->crw -= w; + tw += (usize)w; + if (!r->cr.l || w != 0) break; + } + if (wo) *wo = tw; + return 1; +} + +static inline int +rl_insert(struct readline *r, u32 c, const u8 *b, usize l, usize *n) +{ + if (r->cl.l + l >= RL_CAP) return RL_NONE; + u8 w = utf8_width(c); + str_copy(&r->cl, (const char *)b, l); + r->clw += w; + if (n) *n = w; + if (r->vx + w < r->vw) { + r->vx += w; + return RL_PARTIAL; + } else { + r->vx = r->vw - w; + return RL_FULL; + } +} + +static inline int +rl_backspace(struct readline *r, usize *n) +{ + if (!r->cl.l) return RL_NONE; + usize w = rl_consume_cl(r); + if (n) *n = w; + usize c = rl_cursor(r); + if (rl_total(r) < r->vw && r->vx == c + w) { + r->vx = c; + return RL_PARTIAL; + } + if (r->vx > c) r->vx = c; + return RL_FULL; +} + +static inline int +rl_delete(struct readline *r, usize *n) +{ + if (n) *n = 0; + if (!r->cr.l) return RL_NONE; + usize w = rl_consume_cr(r); + if (n) *n = w; + return r->vx + r->crw + w < r->vw ? RL_PARTIAL : RL_FULL; +} + +static inline int +rl_delete_left(struct readline *r) +{ + if (!r->cl.l) return RL_NONE; + r->cl.l = 0; + r->clw = 0; + r->vx = rl_prompt(r); + return RL_FULL; +} + +static inline int +rl_delete_right(struct readline *r) +{ + if (!r->cr.l) return RL_NONE; + r->cr.l = 0; + r->crw = 0; + return RL_PARTIAL; +} + +static inline int +rl_left(struct readline *r, usize *n) +{ + if (n) *n = 0; + if (!r->cl.l) { + if (!rl_offset(r)) return RL_NONE; + r->vx = rl_cursor(r); + return RL_FULL; + } + usize w; + if (rl_take_left_to_right(r, &w) < 0) return RL_NONE; + if (n) *n = w; + if (r->vx >= w && r->vx - w > 0) { + r->vx -= w; + return RL_PARTIAL; + } + return RL_FULL; +} + +static inline int +rl_right(struct readline *r, usize *n) +{ + if (!r->cr.l) return RL_NONE; + usize w; + if (rl_take_right_to_left(r, &w) < 0) return RL_NONE; + if (n) *n = w; + if (r->vx + w + w <= r->vw) { + r->vx += w; + return RL_PARTIAL; + } + return RL_FULL; +} + +static inline void +rl_join(struct readline *r) +{ + str_copy(&r->cl, rl_cr_ptr(r), r->cr.l); + str_terminate(&r->cl); + r->clw += r->crw; + r->cr.l = 0; + r->crw = 0; +} + +static inline int +rl_home(struct readline *r) +{ + if (!r->cl.l) return RL_NONE; + if (r->cr.l + r->cl.l > RL_CAP) return RL_NONE; + usize s = rl_offset(r); + usize o = r->cr.c - r->cr.l - r->cl.l; + memcpy(r->cr.m + o, r->cl.m, r->cl.l); + r->cr.l += r->cl.l; + r->crw += r->clw; + r->cl.l = 0; + r->clw = 0; + r->vx = rl_prompt(r); + return s ? RL_FULL : RL_PARTIAL; +} + +static inline int +rl_end(struct readline *r) +{ + if (!r->cr.l) return RL_NONE; + if (r->cl.l + r->cr.l > RL_CAP) return RL_NONE; + str_copy(&r->cl, rl_cr_ptr(r), r->cr.l); + r->clw += r->crw; + r->cr.l = 0; + r->crw = 0; + usize c = rl_cursor(r); + if (c < r->vw) { + r->vx = c; + return RL_PARTIAL; + } + u32 cp; + int w; + rl_cl_last(r, &cp, &w); + r->vx = r->vw - w; + return RL_FULL; +} + +static inline int +rl_word_left(struct readline *r) +{ + if (!r->cl.l) return RL_NONE; + u32 cp; + int w; + while (r->cl.l) { + rl_cl_last(r, &cp, &w); + if (!rl_is_ifs(cp)) break; + if (rl_left(r, NULL) == RL_NONE) break; + } + while (r->cl.l) { + rl_cl_last(r, &cp, &w); + if (rl_is_ifs(cp)) break; + if (rl_left(r, NULL) == RL_NONE) break; + } + return RL_FULL; +} + +static inline int +rl_word_right(struct readline *r) +{ + if (!r->cr.l) return RL_NONE; + u32 cp; + int w; + while (r->cr.l) { + rl_cr_first(r, &cp, &w); + if (!rl_is_ifs(cp)) break; + if (rl_right(r, NULL) == RL_NONE) break; + } + while (r->cr.l) { + rl_cr_first(r, &cp, &w); + if (rl_is_ifs(cp)) break; + if (rl_right(r, NULL) == RL_NONE) break; + } + return RL_FULL; +} + +static inline int +rl_delete_word_prev(struct readline *r) +{ + if (!r->cl.l) return -1; + usize d = 0; + u32 cp; + int w; + for (; r->cl.l; d++) { + rl_cl_last(r, &cp, &w); + if (!rl_is_ifs(cp)) break; + if (rl_backspace(r, NULL) == RL_NONE) break; + } + for (; r->cl.l; d++) { + rl_cl_last(r, &cp, &w); + if (rl_is_ifs(cp)) break; + if (rl_backspace(r, NULL) == RL_NONE) break; + } + return d ? RL_FULL : RL_NONE; +} + +static inline int +rl_delete_word_right(struct readline *r) +{ + if (!r->cr.l) return RL_NONE; + usize d = 0; + u32 cp; + int w; + for (; r->cr.l; d++) { + rl_cr_first(r, &cp, &w); + if (!rl_is_ifs(cp)) break; + if (rl_delete(r, NULL) == RL_NONE) break; + } + for (; r->cr.l; d++) { + rl_cr_first(r, &cp, &w); + if (rl_is_ifs(cp)) break; + if (rl_delete(r, NULL) == RL_NONE) break; + } + return d ? RL_FULL : RL_NONE; +} + +static inline usize +rl_write_seg(str *s, const unsigned char *p, usize l, usize c, usize x, usize e) +{ + const unsigned char *pe = p + l; + u32 cp; + while (p < pe) { + const unsigned char *nc = utf8_decode((void *)p, &cp); + int w = utf8_width(cp); + if (c < x && c + w > x) + str_push_c(s, ' '); + else if (c + w > x && c + w <= e) + str_push(s, (const char *)p, (usize)(nc - p)); + c += w; + if (c >= e) return c; + p = nc; + } + return c; +} + +static inline void +rl_write_range(const struct readline *r, str *s, usize x, usize n) +{ + usize c = 0; + usize e = x + n; + c = rl_write_seg(s, (const unsigned char *)r->pr.d, r->pr.l, c, x, e); + if (c >= e) return; + c = rl_write_seg(s, (const unsigned char *)r->cl.m, r->cl.l, c, x, e); + if (c >= e) return; + c = rl_write_seg(s, (const unsigned char *)rl_cr_ptr(r), r->cr.l, c, x, e); + if (c < e) str_memset(s, ' ', e - c); +} + +static inline void +rl_write_visible(const struct readline *r, str *s) +{ + rl_write_range(r, s, rl_offset(r), r->vw); +} + +#endif // DYLAN_READLINE + diff --git a/lib/str.h b/lib/str.h new file mode 100644 index 0000000..6464dcf --- /dev/null +++ b/lib/str.h @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2026 Dylan Araps + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef DYLAN_STR_H +#define DYLAN_STR_H + +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + +#include "util.h" + +struct str; +typedef usize (str_err)(struct str *, void *, usize l); + +typedef struct str { + char *m; + usize l; + usize c; + + str_err *f; + void *ctx; +} str; + +#define STR_ERR ((usize) -1) +#define STR_PUSH(s, p) str_push((s), (p), sizeof(p) - 1) +#define STR_COPY(s, p) str_copy((s), (p), sizeof(p) - 1) + +#define STR_INIT(s, m, f, ctx) do { \ + static char b[(m)]; \ + str_init((s), (b), sizeof(b), (f), (ctx)); \ +} while (0) + +static inline void +str_init(str *s, char *b, usize c, str_err *f, void *ctx) +{ + s->m = b; + s->l = -(!b || !c); + s->c = c; + s->f = f; + s->ctx = ctx; +} + +static inline usize +str_cb(str *s, usize l) +{ + return s->f ? s->f(s, s->ctx, l) : STR_ERR; +} + +static inline int +str_fit(str *s, usize l) +{ + if (s->l + l < s->c) return 1; + return str_cb(s, l) != STR_ERR; +} + +static inline void +str_copy(str *s, const char *p, usize l) +{ + memcpy(&s->m[s->l], p, l); + s->l += l; +} + +static inline void +str_copy_c(str *s, char c) +{ + s->m[s->l++] = c; +} + +static inline cut +str_push(str *s, const char *p, usize l) +{ + if (!str_fit(s, l)) return (cut) { 0, 0 }; + str_copy(s, p, l); + return (cut){ &s->m[s->l - l], l }; +} + +static inline void +str_push_c(str *s, char c) +{ + if (!str_fit(s, 1)) return; + str_copy_c(s, c); +} + +static inline void +str_push_s(str *s, const char *p) +{ + str_push(s, p, strlen(p)); +} + +static inline void +str_memset(str *s, int c, usize n) +{ + if (!str_fit(s, n)) return; + memset(&s->m[s->l], c, n); + s->l += n; +} + +static inline void +str_push_u32_b(str *s, u32 v, u32 b, int c, usize l) +{ + static const char d[] = "0123456789abcdef"; + char o[33]; + char *p = &o[sizeof(o)]; + assert(b >= 2 && b <= 16); + do { + *--p = d[v % b]; + v /= b; + } while (v); + usize n = (usize)(&o[sizeof(o)] - p); + if (n < l) str_memset(s, c, l - n); + str_push(s, p, n); +} + +static inline void +str_push_u32_p(str *s, u32 v, int c, usize l) +{ + str_push_u32_b(s, v, 10, c, l); +} + +static inline void +str_push_u32(str *s, u32 v) +{ + str_push_u32_p(s, v, 0, 0); +} + +static inline void +str_push_u64(str *s, u64 v) +{ + char b[21]; + char *p = &b[sizeof(b)]; + do { + *--p = (char)('0' + (v % 10)); + v /= 10; + } while (v); + str_push(s, p, (usize)(&b[sizeof(b)] - p)); +} + +static inline void +str_push_sanitize(str *s, const char *p, usize l) +{ + if (!str_fit(s, l)) return; + char *d = s->m + s->l; + const unsigned char *b = (const unsigned char *) p; + for (usize i = 0; i < l; i++) { + unsigned char c = b[i]; + d[i] = (char)(c >= 0x20 && c != 0x7F ? c : '?'); + } + s->l += l; +} + +static inline int +str_cmp(str *a, str *b) +{ + return a->l == b->l && *a->m == *b->m && !memcmp(a->m, b->m, b->l); +} + +static inline void +str_terminate(str *s) +{ + if (str_fit(s, 1)) s->m[s->l] = 0; +} + +#endif // DYLAN_STR_H + diff --git a/lib/term.h b/lib/term.h new file mode 100644 index 0000000..b3b1440 --- /dev/null +++ b/lib/term.h @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2026 Dylan Araps + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef DYLAN_TERM_H +#define DYLAN_TERM_H + +#include <fcntl.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <termios.h> +#include <unistd.h> + +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/time.h> +#include <sys/wait.h> + +#include "util.h" +#include "vt.h" + +enum { + TERM_LOADED = 1 << 0, + TERM_RESIZE = 1 << 1, +}; + +static struct term { + struct termios o; + int fd; + int null; + volatile sig_atomic_t flag; + volatile sig_atomic_t dead; +} *TERM; + +static inline void +term_set_dead(struct term *t, int s) +{ + t->dead = 128 + s; +} + +static inline int +term_dead(const struct term *t) +{ + return t->dead; +} + +static inline int +term_resize(const struct term *t) +{ + return t->flag & TERM_RESIZE; +} + +static inline void +term_restore_on_signal(int s) +{ + if (!TERM) return; + if (!(TERM->flag & TERM_LOADED)) return; + term_set_dead(TERM, s); + tcsetattr(TERM->fd, TCSAFLUSH, &TERM->o); + + // + // TODO: Unhardcode this. + // +#define TERM_COOKED \ + S(VT_ED0 VT_BPASTE_OFF VT_DECAWM_Y VT_DECTCEM_Y VT_ALT_SCREEN_N) + write_all(TERM->fd, TERM_COOKED); + write_all(STDOUT_FILENO, TERM_COOKED); +} + +static inline void +term_signal_fatal(int s) +{ + term_restore_on_signal(s); + _exit(128 + s); +} + +static inline void +term_signal_crash(int s) +{ + term_restore_on_signal(s); + struct sigaction sa = {0}; + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sigaction(s, &sa, NULL); + kill(getpid(), s); +} + +static inline void +term_signal_sigwinch(int s) +{ + (void) s; + if (TERM) TERM->flag |= TERM_RESIZE; +} + +static inline void +term_signal_setup(void) { + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = term_signal_fatal; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sa.sa_handler = term_signal_crash; + sigaction(SIGSEGV, &sa, NULL); + sigaction(SIGABRT, &sa, NULL); + sigaction(SIGBUS, &sa, NULL); + sigaction(SIGFPE, &sa, NULL); + sigaction(SIGILL, &sa, NULL); + sa.sa_handler = term_signal_sigwinch; + sigaction(SIGWINCH, &sa, NULL); +} + +static inline int +term_size_update(struct term *t, u16 *row, u16 *col) +{ + struct winsize ws; + if (ioctl(t->fd, TIOCGWINSZ, &ws) < 0) + return -1; + t->flag &= ~TERM_RESIZE; + *row = ws.ws_row; + *col = ws.ws_col; + return 0; +} + +static inline int +term_init_io(struct term *t) +{ + if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) + t->fd = STDIN_FILENO; + else if (isatty(STDIN_FILENO)) { + t->fd = open("/dev/tty", O_RDWR|O_CLOEXEC); + if (t->fd < 0) return -1; + } else { + t->fd = -1; + return -1; + } + t->null = open("/dev/null", O_WRONLY|O_CLOEXEC); + return t->null; +} + +static inline int +term_raw(const struct term *t) +{ + struct termios n = t->o; + n.c_iflag &= ~(BRKINT|ICRNL|INPCK|ISTRIP|IXON); + n.c_oflag &= ~(OPOST); + n.c_cflag |= (CS8); + n.c_lflag &= ~(ECHO|ICANON|IEXTEN|ISIG); + n.c_cc[VMIN] = 1; + n.c_cc[VTIME] = 0; + return tcsetattr(t->fd, TCSAFLUSH, &n); +} + +static inline int +term_cooked(struct term *t) +{ + assert(t->flag & TERM_LOADED); + return tcsetattr(t->fd, TCSAFLUSH, &t->o); +} + +static inline int +term_init(struct term *t) +{ + if (term_init_io(t) < 0) return -1; + if (tcgetattr(t->fd, &t->o) < 0) return -1; + TERM = t; + t->flag |= TERM_LOADED; + term_signal_setup(); + return 0; +} + +static inline void +term_reap(void) +{ + for (int st; waitpid(-1, &st, WNOHANG) > 0; ); +} + +static inline void +term_destroy(const struct term *t) +{ + if (t->fd >= 0) close(t->fd); + if (t->null >= 0) close(t->null); + TERM = NULL; +} + +#endif // DYLAN_TERM_H + diff --git a/lib/term_key.h b/lib/term_key.h new file mode 100644 index 0000000..f92ef80 --- /dev/null +++ b/lib/term_key.h @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2026 Dylan Araps + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef DYLAN_TERM_KEY_H +#define DYLAN_TERM_KEY_H + +#include <fcntl.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <termios.h> +#include <unistd.h> + +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/time.h> + +#include "utf8.h" +#include "util.h" + +// +// Store encoded special keys alongside utf8 codepoints in an unused range. +// +#define KEY_TAG 0x80000000u +#define KEY_SYM 0x40000000u +#define KEY_MOD_SHIFT 27u +#define KEY_MOD_MASK (0x7u << KEY_MOD_SHIFT) +#define KEY_TXT_MASK 0x001FFFFFu +#define KEY_SYM_MASK 0x000000FFu +#define KEY_IS_SYM(k) ((((u32)(k)) & (KEY_TAG|KEY_SYM)) == (KEY_TAG|KEY_SYM)) +#define KEY_GET_MOD(k) ((((u32)(k)) & KEY_MOD_MASK) >> KEY_MOD_SHIFT) + +#define K(m, c) \ +((u32)(m) == 0u ? (u32)(c) : (KEY_IS_SYM(c) \ +? (((u32)(c) & ~KEY_MOD_MASK) | (((u32)(m) & 0x7u) << KEY_MOD_SHIFT)) \ +: (KEY_TAG | (((u32)(m) & 0x7u) << KEY_MOD_SHIFT) | ((u32)(c) & KEY_TXT_MASK)))) + +#define KEY_REG(id) (KEY_TAG|KEY_SYM | ((u32)(id) & KEY_SYM_MASK)) + +#define MOD_SHIFT (1u << 0) +#define MOD_ALT (1u << 1) +#define MOD_CTRL (1u << 2) + +#define KEY_ESCAPE 27 +#define KEY_BACKSPACE 127 +#define KEY_TAB K(MOD_CTRL, 'i') +#define KEY_SHIFT_TAB K(MOD_SHIFT|MOD_CTRL, 'i') +#define KEY_ENTER K(MOD_CTRL, 'm') +#define KEY_UP KEY_REG(1) +#define KEY_DOWN KEY_REG(2) +#define KEY_LEFT KEY_REG(3) +#define KEY_RIGHT KEY_REG(4) +#define KEY_HOME KEY_REG(5) +#define KEY_END KEY_REG(6) +#define KEY_PAGE_UP KEY_REG(7) +#define KEY_PAGE_DOWN KEY_REG(8) +#define KEY_INSERT KEY_REG(9) +#define KEY_DELETE KEY_REG(10) +#define KEY_F1 KEY_REG(11) +#define KEY_F2 KEY_REG(12) +#define KEY_F3 KEY_REG(13) +#define KEY_F4 KEY_REG(14) +#define KEY_F5 KEY_REG(15) +#define KEY_F6 KEY_REG(16) +#define KEY_F7 KEY_REG(17) +#define KEY_F8 KEY_REG(18) +#define KEY_F9 KEY_REG(19) +#define KEY_F10 KEY_REG(20) +#define KEY_F11 KEY_REG(21) +#define KEY_F12 KEY_REG(22) +#define KEY_PASTE KEY_REG(23) +#define KEY_PASTE_END KEY_REG(24) + +struct term_key { + u8 b[64]; + u16 l; + u32 c; +}; + +static inline u32 +term_key_csi_tilde(u8 c, u32 m) +{ + switch (c) { + case 1: return K(m, KEY_HOME); + case 2: return K(m, KEY_INSERT); + case 3: return K(m, KEY_DELETE); + case 4: return K(m, KEY_END); + case 5: return K(m, KEY_PAGE_UP); + case 6: return K(m, KEY_PAGE_DOWN); + case 7: return K(m, KEY_HOME); + case 8: return K(m, KEY_END); + case 11: return K(m, KEY_F1); + case 12: return K(m, KEY_F2); + case 13: return K(m, KEY_F3); + case 14: return K(m, KEY_F4); + case 15: return K(m, KEY_F5); + case 17: return K(m, KEY_F6); + case 18: return K(m, KEY_F7); + case 19: return K(m, KEY_F8); + case 20: return K(m, KEY_F9); + case 21: return K(m, KEY_F10); + case 23: return K(m, KEY_F11); + case 24: return K(m, KEY_F12); + case 200: return KEY_PASTE; + case 201: return KEY_PASTE_END; + default: return 0; + } +} + +static inline u32 +term_key_csi_final(u8 c, u32 m) +{ + switch (c) { + case 'A': return K(m, KEY_UP); + case 'B': return K(m, KEY_DOWN); + case 'C': return K(m, KEY_RIGHT); + case 'D': return K(m, KEY_LEFT); + case 'H': return K(m, KEY_HOME); + case 'F': return K(m, KEY_END); + case 'Z': return K(MOD_SHIFT|MOD_CTRL | m, 'i'); + default: return 0; + } +} + +static inline u32 +term_key_csi_ss3(u8 c) +{ + switch (c) { + case 'P': return K(0, KEY_F1); + case 'Q': return K(0, KEY_F2); + case 'R': return K(0, KEY_F3); + case 'S': return K(0, KEY_F4); + case 'A': return K(0, KEY_UP); + case 'B': return K(0, KEY_DOWN); + case 'C': return K(0, KEY_RIGHT); + case 'D': return K(0, KEY_LEFT); + case 'H': return K(0, KEY_HOME); + case 'F': return K(0, KEY_END); + default: return 0; + } +} + +static inline s32 +term_key_csi_int(const u8 *b, usize l, usize *i) +{ + u32 v = 0; + usize j = *i; + if (j >= l || b[j] < '0' || b[j] > '9') return -1; + for (; j < l && b[j] >= '0' && b[j] <= '9'; j++) + v = v * 10u + (u32)(b[j] - '0'); + *i = j; + return (s32) v; +} + +static inline u32 +term_key_csi_xterm_mod(u32 x) +{ + u32 m = 0; + if (x < 2 || x > 8) return m; + if (x & 1u) m |= MOD_ALT; + if (x & 2u) m |= MOD_SHIFT; + if (x & 4u) m |= MOD_CTRL; + return m; +} + +static inline bool +term_key_csi_end(u8 c) +{ + return c >= 0x40 && c <= 0x7E; +} + +static inline bool +term_key_csi_read(int fd, struct term_key *k) +{ + for (;;) { + if (k->l >= sizeof(k->b)) + return false; + if (read(fd, &k->b[k->l], 1) != 1) + return false; + u8 c = k->b[k->l++]; + if (term_key_csi_end(c)) + break; + } + return true; +} + +static inline bool +term_key_ss3(int fd, struct term_key *k) +{ + if (read(fd, &k->b[k->l], 1) != 1) return false; + k->l++; + k->c = term_key_csi_ss3(k->b[2]); + return !!k->c; +} + +static inline bool +term_key_csi(int fd, struct term_key *k) +{ + if (!term_key_csi_read(fd, k)) return false; + usize n = k->l; + if (n < 3) return false; + u8 f = k->b[n - 1]; + usize i = 2; + if (i < n && (k->b[i] == '?' || k->b[i] == '>' || k->b[i] == '<')) + i++; + s32 p1 = term_key_csi_int(k->b, n, &i); + s32 p2 = -1; + if (p1 != -1 && i < n && k->b[i] == ';') { + i++; + p2 = term_key_csi_int(k->b, n, &i); + } + u32 m = p2 != -1 ? term_key_csi_xterm_mod(p2) : 0; + if (f == '~' && p1 != -1) + k->c = term_key_csi_tilde(p1, m); + else + k->c = term_key_csi_final(f, m); + return !!k->c; +} + +static inline bool +term_key_utf8(int fd, struct term_key *k, usize o, u32 m) +{ + u8 c = k->b[o]; + if (c >= 1 && c <= 26) { + k->c = K(m | MOD_CTRL, 'a' + (c - 1)); + k->l = o + 1; + return true; + } + if (c >= 0xC0) { + usize l = utf8_expected(c); + if (!l) return false; + for (usize i = 1; i < l; i++) + if (read(fd, &k->b[o + i], 1) != 1) + return false; + int e; + u32 cp; + utf8_decode_untrusted(&k->b[o], &cp, &e); + if (e) return false; + k->c = K(m, cp); + k->l = o + l; + return true; + } + k->c = K(m, c); + k->l = o + 1; + return true; +} + +static inline bool +term_key_read(int fd, struct term_key *k) +{ + if (read(fd, k->b, 1) != 1) return false; + k->l = 1; + if (unlikely(k->b[0] == '\033')) { + struct timeval tv = { .tv_sec = 0, .tv_usec = 30000 }; + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + if (select(fd + 1, &rfds, NULL, NULL, &tv) <= 0) { + k->c = k->b[0]; + return true; + } + if (read(fd, &k->b[1], 1) != 1) return false; + k->l = 2; + switch (k->b[1]) { + case '[': return term_key_csi(fd, k); + case 'O': return term_key_ss3(fd, k); + default: return term_key_utf8(fd, k, 1, MOD_ALT); + } + } + return term_key_utf8(fd, k, 0, 0); +} + +#endif // DYLAN_TERM_KEY_H + diff --git a/lib/utf8.h b/lib/utf8.h new file mode 100644 index 0000000..0104661 --- /dev/null +++ b/lib/utf8.h @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2026 Dylan Araps + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef DYLAN_UTF8_H +#define DYLAN_UTF8_H + +#include "util.h" + +static inline usize +utf8_expected(u8 b) +{ + static const u8 L[] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,2,2,2,2,3,3,4,0 + }; + return L[b >> 3]; +} + +static inline int +utf8_width(u32 c) +{ + if (c == 0) return 0; + + // Control. + if (c < 0x20) return 0; + if (c >= 0x7f && c < 0xa0) return 0; + + // Zero width joiner. + if (c == 0x200d) return 0; + + // Combining. + if ((c >= 0x0300 && c <= 0x036f) || + (c >= 0x1ab0 && c <= 0x1aff) || + (c >= 0x1dc0 && c <= 0x1dff) || + (c >= 0x20d0 && c <= 0x20ff) || + (c >= 0xfe20 && c <= 0xfe2f) || + (c >= 0xe0100 && c <= 0xe01ef)) + return 0; + + // Variation selectors. + if ((c >= 0xfe00 && c <= 0xfe0f)) + return 0; + + // Emoji modifiers. + if (c >= 0x1f3fb && c <= 0x1f3ff) + return 0; + + // East asian wide. + if ((c >= 0x1100 && c <= 0x115f) || + c == 0x2329 || c == 0x232a || + (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || + (c >= 0xac00 && c <= 0xd7a3) || + (c >= 0xf900 && c <= 0xfaff) || + (c >= 0xfe10 && c <= 0xfe19) || + (c >= 0xfe30 && c <= 0xfe6f) || + (c >= 0xff00 && c <= 0xff60) || + (c >= 0xffe0 && c <= 0xffe6) || + (c >= 0x20000 && c <= 0x2fffd) || + (c >= 0x30000 && c <= 0x3fffd)) + return 2; + + // Emoji block. + if ((c >= 0x1f300 && c <= 0x1faff) || + (c >= 0x2600 && c <= 0x27bf) || + (c >= 0x2b50 && c <= 0x2b55)) + return 2; + + return 1; +} + +// +// Branchless UTF8 decoder by Skeeto. +// Source: https://nullprogram.com/blog/2017/10/06/ +// +static inline void * +utf8_decode(void *b, u32 *c) +{ + unsigned char *s = (unsigned char *)b; + usize l = utf8_expected(s[0]); + static const int m[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; + static const int shc[] = {0, 18, 12, 6, 0}; + *c = (u32)(s[0] & m[l]) << 18; + *c |= (u32)(s[1] & 0x3f) << 12; + *c |= (u32)(s[2] & 0x3f) << 6; + *c |= (u32)(s[3] & 0x3f); + *c >>= shc[l]; + return s + l + !l; +} + +static void * +utf8_decode_untrusted(void *b, u32 *c, int *e) +{ + static const u32 mi[] = {4194304, 0, 128, 2048, 65536}; + static const int she[] = {0, 6, 4, 2, 0}; + unsigned char *s = (unsigned char *)b; + unsigned char *n = utf8_decode(b, c); + usize l = utf8_expected(s[0]); + *e = (*c < mi[l]) << 6; // Non-canonical encoding. + *e |= ((*c >> 11) == 0x1b) << 7; // Surrogate half? + *e |= (*c > 0x10FFFF) << 8; // Out of range? + *e |= (s[1] & 0xc0) >> 2; + *e |= (s[2] & 0xc0) >> 4; + *e |= (s[3]) >> 6; + *e ^= 0x2a; // Top two bits of each tail byte correct? + *e >>= she[l]; + return n; +} + +static inline usize +utf8_decode_rev(const unsigned char *s, usize x, u32 *c) +{ + usize i = x; + while (i > 0 && (s[i - 1] & 0xc0) == 0x80) i--; + if (i > 0) i--; + usize l = x - i; + utf8_decode((void *)(s + i), c); + return l; +} + +static inline usize +utf8_cols(const void *s, usize l, usize *lw) +{ + usize w = 0; + const unsigned char *p = (const unsigned char *)s; + const unsigned char *e = p + l; + *lw = 0; + while (p < e) { + u32 cp; + p = utf8_decode((void *)p, &cp); + *lw = utf8_width(cp); + w += *lw; + } + return w; +} + +static inline usize +utf8_trunc_narrow(const char *s, usize l, usize c) +{ + const unsigned char *p = (const unsigned char *)s; + const unsigned char *e = p + l; + for (usize i = 0; p < e && i < c; i++) { + unsigned char b = *p++; + if (!(b & 0x80)) continue; + for (; p < e && ((*p & 0xC0) == 0x80); p++); + } + return (usize)(p - (const unsigned char *)s); +} + +static inline usize +utf8_trunc_wide(const char *s, usize l, usize c) +{ + const unsigned char *p = (const unsigned char *)s; + const unsigned char *e = p + l; + for (usize i = 0; p < e && i < c; ) { + u32 cp; + const unsigned char *n = (const unsigned char *)utf8_decode((void *)p, &cp); + usize a = (usize)(n - p); + if (!a) a = 1; + int w = utf8_width(cp); + if (i + w > c) break; + i += w; + p += a; + } + return (usize)(p - (const unsigned char *)s); +} + +#endif // DYLAN_UTF8_H + diff --git a/lib/util.h b/lib/util.h new file mode 100644 index 0000000..819e939 --- /dev/null +++ b/lib/util.h @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2026 Dylan Araps + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef DYLAN_UTIL_H +#define DYLAN_UTIL_H + +#include <assert.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <pwd.h> +#include <spawn.h> +#include <stdalign.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> + +#define ARR_SIZE(a) ((intptr_t)(sizeof(a) / sizeof(*(a)))) +#define IS_POW2(x) ((x) > 0 && (((x) & ((x) - 1)) == 0)) +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define MAX(x, y) ((x) > (y) ? (x) : (y)) + +#if defined(__GNUC__) || defined(__clang__) +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#else +#define likely(x) !!(x) +#define unlikely(x) !!(x) +#endif + +#if __STDC_VERSION__ >= 201112L +#define STATIC_ASSERT _Static_assert +#else +#define SA_CAT_(a,b) a##b +#define SA_CAT(a,b) SA_CAT_(a,b) +#define STATIC_ASSERT(c, m) \ + enum { SA_CAT(static_assert_line_, __LINE__) = 1 / (!!(c)) } +#endif + +typedef union { + long long ll; + long double ld; + void *p; +} align_max; + +typedef uint64_t u64; +typedef uint32_t u32; +typedef uint16_t u16; +typedef uint8_t u8; +typedef int64_t s64; +typedef int32_t s32; +typedef int16_t s16; +typedef int8_t s8; +typedef size_t usize; +typedef ptrdiff_t size; + +typedef struct { + const char *d; + usize l; +} cut; + +// +// Add four bytes to the end of the CUT to allow the branchless utf8 decoder to +// potentially overread the string. +// +#define CUT(s) (cut) { s "\0\0\0\0", sizeof(s) - 1 } +#define CUT_NULL ((cut){0}) +#define STR_NULL (&(str){0}) +#define S(s) (s), (sizeof(s) - 1) + +static inline int +cut_cmp(cut a, cut b) +{ + return a.l == b.l && *a.d == *b.d && !memcmp(a.d, b.d, b.l); +} + +static inline u64 +bitfield_get64(u64 v, u8 s, u8 b) +{ + return (v >> s) & ((1ULL << b) - 1); +} + +static inline void +bitfield_set64(u64 *t, u64 v, u8 s, u8 b) +{ + u64 m = ((1ULL << b) - 1) << s; + *t = (*t & ~m) | ((v << s) & m); +} + +static inline u32 +bitfield_get32(u32 v, int o, int l) +{ + return (v >> o) & ((1u << l) - 1); +} + +static inline void +bitfield_set32(u32 *v, u32 x, int o, int l) +{ + u32 m = ((1u << l) - 1) << o; + *v = (*v & ~m) | ((x << o) & m); +} + +static inline void +bitfield_set8(u8 *t, u8 v, u8 s, u8 b) +{ + u8 m = ((1ULL << b) - 1) << s; + *t = (*t & ~m) | ((v << s) & m); +} + +static inline cut +get_env(const char *e, const char *f) +{ + const char *p = getenv(e); + cut r; + r.d = p && *p ? p : f; + r.l = r.d ? strlen(r.d) : 0; + return r; +} + +static inline int +write_all(int fd, const char *b, usize l) +{ + for (usize o = 0; o < l; ) { + ssize_t r = write(fd, b + o, l - o); + if (r > 0) { + o += (usize) r; + continue; + } + if (r == -1 && errno == EINTR) + continue; + return -1; + } + return 0; +} + +static inline int +run_cmd(int tty, int in, const char *d, const char *const a[], bool bg) +{ + extern char **environ; + posix_spawn_file_actions_t fa; + pid_t pid; + int rc; + int st; + pid_t r; + rc = posix_spawn_file_actions_init(&fa); + if (rc) { errno = rc; return -1; } + if (in >= 0) { + rc = posix_spawn_file_actions_adddup2(&fa, in, 0); + if (rc) goto fail_fa; + } + if (tty >= 0) { + if ((rc = posix_spawn_file_actions_adddup2(&fa, tty, 1)) || + (rc = posix_spawn_file_actions_adddup2(&fa, tty, 2))) + goto fail_fa; + } + if (d) { +#if defined(_POSIX_VERSION) && _POSIX_VERSION >= 202405L + rc = posix_spawn_file_actions_addchdir(&fa, d); +#elif defined(_GNU_SOURCE) || defined(_BSD_SOURCE) + rc = posix_spawn_file_actions_addchdir_np(&fa, d); +#else + posix_spawn_file_actions_destroy(&fa); + pid = fork(); + if (pid == -1) return -1; + if (!pid) { + if (in >= 0) { + if (dup2(in, 0) == -1) _exit(127); + if (in != 0) close(in); + } + if (tty >= 0) { + if (dup2(tty, 1) == -1) _exit(127); + if (dup2(tty, 2) == -1) _exit(127); + if (tty != 1 && tty != 2) close(tty); + } + if (d && chdir(d) == -1) + _exit(127); + execvp(a[0], (char *const *)a); + _exit(127); + } + goto end; +#endif + if (rc) goto fail_fa; + } + rc = posix_spawnp(&pid, a[0], &fa, NULL, (char *const *)a, environ); + posix_spawn_file_actions_destroy(&fa); + if (rc) { errno = rc; return -1; } + goto end; // Silence compiler warning when ifdefs cause no jump. +end: + if (bg) return 0; + do r = waitpid(pid, &st, 0); + while (r == -1 && errno == EINTR); + return r == -1 ? -1 : st; +fail_fa: + posix_spawn_file_actions_destroy(&fa); + errno = rc; + return -1; +} + +static inline int +fd_from_buf(const char *b, usize l) +{ + int fd[2]; + if (pipe(fd)) return -1; + if (l > PIPE_BUF) { +#ifdef F_GETPIPE_SZ + int c = fcntl(fd[1], F_GETPIPE_SZ); + if (c < 0 || l > (usize)c) { + close(fd[0]); + close(fd[1]); + return -1; + } +#else + close(fd[0]); + close(fd[1]); + return -1; +#endif + } + ssize_t w = write(fd[1], b, l); + close(fd[1]); + if (w < 0 || (usize)w != l) { + close(fd[0]); + return -1; + } + return fd[0]; +} + +static inline usize +u64_popcount(u64 x) +{ +#if defined(__GNUC__) || defined(__clang__) + return (usize) __builtin_popcountll(x); +#else + usize c = 0; + for (; x; x &= x - 1) c++; + return c; +#endif +} + +static inline u64 +u64_ctz(u64 x) +{ +#if defined(__GNUC__) || defined(__clang__) + return (u64)__builtin_ctzll(x); +#else + u64 i = 0; + while (!(x & 1ull)) { x >>= 1; i++; } + return i; +#endif +} + +static inline u64 +u64_clz(u64 x) +{ +#if defined(__GNUC__) || defined(__clang__) + return (u64)__builtin_clzll(x); +#else + u64 i = 0; + for (u64 m = 1ull << 63; !(x & m); m >>= 1) i++; + return i; +#endif +} + +static inline u32 +hash_fnv1a32(const char *d, usize l) +{ + u32 h = 2166136261u; + for (usize i = 0; i < l; i++) + h = (h ^ (unsigned char)d[i]) * 16777619u; + return h | 1; +} + +static inline s64 +tz_offset(void) +{ + time_t n = time(NULL); + struct tm lt; + struct tm gt; + if (!localtime_r(&n, <)) return 0; + if (!gmtime_r(&n, >)) return 0; + time_t lo = mktime(<); + time_t gm = mktime(>); + if (lo == (time_t)-1 || gm == (time_t)-1) return 0; + return (s64)(lo - gm); +} + +static inline usize +fm_path_resolve(char *s, usize l) +{ + char *m = s; + usize i = 0, w = 0; + while (i < l) { + while (i < l && m[i] == '/') i++; + if (i >= l) break; + usize b = i; + while (i < l && m[i] != '/') i++; + usize n = i - b; + if (n == 1 && m[b] == '.') + continue; + if (n == 2 && m[b] == '.' && m[b + 1] == '.') { + if (w > 1) { + if (m[w - 1] == '/') w--; + while (w > 1 && m[w - 1] != '/') w--; + } + continue; + } + if (w == 0 || m[w - 1] != '/') + m[w++] = '/'; + if (w != b) memmove(m + w, m + b, n); + w += n; + } + if (w > 1 && m[w - 1] == '/') w--; + if (!w) m[w++] = '/'; + m[w] = 0; + return w; +} + +#endif // DYLAN_UTIL_H + diff --git a/lib/vt.h b/lib/vt.h new file mode 100644 index 0000000..4262154 --- /dev/null +++ b/lib/vt.h @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2026 Dylan Araps + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef DYLAN_VT_H +#define DYLAN_VT_H + +#include <stdio.h> +#include <unistd.h> + +#include "str.h" +#include "util.h" + +#define VT_ESC "\x1b" +#define VT_CR "\r" +#define VT_LF "\n" +#define VT_CUU1 VT_ESC "[A" +#define VT_CUU(n) VT_ESC "[" #n "A" +#define VT_CUD1 VT_ESC "[B" +#define VT_CUD(n) VT_ESC "[" #n "B" +#define VT_CUF1 VT_ESC "[C" +#define VT_CUF(n) VT_ESC "[" #n "C" +#define VT_CUB1 VT_ESC "[D" +#define VT_CUB(n) VT_ESC "[" #n "D" +#define VT_CUP(x, y) VT_ESC "[" #x ";" #y "H" +#define VT_CUP1 VT_ESC "[H" +#define VT_DECAWM_Y VT_ESC "[?7h" +#define VT_DECAWM_N VT_ESC "[?7l" +#define VT_DECSC VT_ESC "7" +#define VT_DECRC VT_ESC "8" +#define VT_DECSTBM(x, y) VT_ESC "[" #x ";" #y "r" +#define VT_DECTCEM_Y VT_ESC "[?25h" +#define VT_DECTCEM_N VT_ESC "[?25l" +#define VT_ED0 VT_ESC "[J" +#define VT_ED1 VT_ESC "[1J" +#define VT_ED2 VT_ESC "[2J" +#define VT_EL0 VT_ESC "[K" +#define VT_EL1 VT_ESC "[1K" +#define VT_EL2 VT_ESC "[2K" +#define VT_IL0 VT_ESC "[L" +#define VT_IL(n) VT_ESC "[" #n "L" +#define VT_ICH1 VT_ESC "[@" +#define VT_ICH(n) VT_ESC "[" #n "@" +#define VT_DCH1 VT_ESC "[P" +#define VT_DCH(n) VT_ESC "[" #n "P" +#define VT_SGR0 VT_ESC "[m" + +// +// XTerm Alternate Screen. +// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer +// +#define VT_ALT_SCREEN_Y VT_ESC "[?1049h" +#define VT_ALT_SCREEN_N VT_ESC "[?1049l" + +// +// Synchronized Updates. +// https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036 +// https://github.com/contour-terminal/vt-extensions/blob/master/synchronized-output.md +// +#define VT_BSU VT_ESC "[?2026h" +#define VT_ESU VT_ESC "[?2026l" + +// +// Bracketed Paste. +// +#define VT_BPASTE_ON VT_ESC "[?2004h" +#define VT_BPASTE_OFF VT_ESC "[?2004l" + +// +// VT_SGR(...) macro supporting 16 arguments. +// NOTE: A byte can be saved by using VT_SGR0 instead of VT_SGR(0). +// +#define VT_Fa(f, x) f(x) +#define VT_Fb(f, x, ...) f(x) ";" VT_Fa(f, __VA_ARGS__) +#define VT_Fc(f, x, ...) f(x) ";" VT_Fb(f, __VA_ARGS__) +#define VT_Fd(f, x, ...) f(x) ";" VT_Fc(f, __VA_ARGS__) +#define VT_Fe(f, x, ...) f(x) ";" VT_Fd(f, __VA_ARGS__) +#define VT_Ff(f, x, ...) f(x) ";" VT_Fe(f, __VA_ARGS__) +#define VT_Fg(f, x, ...) f(x) ";" VT_Ff(f, __VA_ARGS__) +#define VT_Fh(f, x, ...) f(x) ";" VT_Fg(f, __VA_ARGS__) +#define VT_Fi(f, x, ...) f(x) ";" VT_Fh(f, __VA_ARGS__) +#define VT_Fj(f, x, ...) f(x) ";" VT_Fi(f, __VA_ARGS__) +#define VT_Fk(f, x, ...) f(x) ";" VT_Fj(f, __VA_ARGS__) +#define VT_Fl(f, x, ...) f(x) ";" VT_Fk(f, __VA_ARGS__) +#define VT_Fm(f, x, ...) f(x) ";" VT_Fl(f, __VA_ARGS__) +#define VT_Fn(f, x, ...) f(x) ";" VT_Fm(f, __VA_ARGS__) +#define VT_Fo(f, x, ...) f(x) ";" VT_Fn(f, __VA_ARGS__) +#define VT_Fp(f, x, ...) f(x) ";" VT_Fo(f, __VA_ARGS__) +#define VT_GET_q(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,...) q +#define VT_CNT(...) VT_GET_q(__VA_ARGS__,p,o,n,m,l,k,j,i,h,g,f,e,d,c,b,a,0) +#define VT_STR(x) #x +#define VT_CAT(a,b) a##b +#define VT_FN(N, f, ...) VT_CAT(VT_F, N)(f, __VA_ARGS__) +#define VT_JOIN(...) VT_FN(VT_CNT(__VA_ARGS__), VT_STR, __VA_ARGS__) +#define VT_SGR(...) VT_ESC "[" VT_JOIN(__VA_ARGS__) "m" + +static inline void +vt_cup(str *s, u32 x, u32 y) +{ + STR_PUSH(s, VT_ESC "["); + str_push_u32(s, y); + str_push_c(s, ';'); + str_push_u32(s, x); + str_push_c(s, 'H'); +} + +static inline void +vt_cuf(str *s, u32 n) +{ + STR_PUSH(s, VT_ESC "["); + str_push_u32(s, n); + str_push_c(s, 'C'); +} + +static inline void +vt_cub(str *s, u32 n) +{ + STR_PUSH(s, VT_ESC "["); + str_push_u32(s, n); + str_push_c(s, 'D'); +} + +static inline void +vt_ich(str *s, u32 n) +{ + STR_PUSH(s, VT_ESC "["); + str_push_u32(s, n); + str_push_c(s, '@'); +} + +static inline void +vt_dch(str *s, u32 n) +{ + STR_PUSH(s, VT_ESC "["); + str_push_u32(s, n); + str_push_c(s, 'P'); +} + +static inline void +vt_decstbm(str *s, u32 x, u32 y) +{ + STR_PUSH(s, VT_ESC "["); + str_push_u32(s, x); + str_push_c(s, ';'); + str_push_u32(s, y); + str_push_c(s, 'r'); +} + +static inline void +vt_sgr(str *s, u32 a, u32 b) +{ + STR_PUSH(s, VT_ESC "["); + str_push_u32(s, a); + str_push_c(s, ';'); + str_push_u32(s, b); + str_push_c(s, 'm'); +} + +#endif // DYLAN_VT_H + |