summaryrefslogtreecommitdiff
path: root/src/fastcsi.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-12 17:49:34 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-12 17:49:34 +0300
commit4384435941b8c7658856b381d071aee2e0035e89 (patch)
tree5ece99c3dd1decb8ec3fd404c0ece2b5865a552a /src/fastcsi.c
term: move the term core to subprojects/term
The core now builds, tests, and benchmarks alone, as a Meson subproject with no dependency on the main tree. - include/term/ holds term.h and the header-only unicode/. src/ holds the core and its private headers. The main tree gets only include/term through term_dep, so it cannot reach a private header. - The term tests, the term benchmarks, the Kitty parser suite, and tools/amalgamate.py and tools/generate_unicode.py move with it. The tests run as term:<name>. - The main build passes its -march (arch_flag) and b_ndebug to the subproject. A directory configured before this commit needs meson configure -Dterm:arch_flag=-march=... once. - The term tests compile with -UNDEBUG. They do work inside assert. - The subproject exports term_test_dep with term_api.h, for the main tree's render, thumbnail, color, and selection tests. - The Kitty bridge used struct vector2i and has not compiled since the public API redesign. It builds again. 2 of its 14 tests fail: REP after a tab, and a cursor report after a resize. - dirgraph.py drops the term and unicode layers. Its self-test uses pty. - The symbols test ignores the __odr_asan symbols that ASan adds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/fastcsi.c')
-rw-r--r--src/fastcsi.c378
1 files changed, 378 insertions, 0 deletions
diff --git a/src/fastcsi.c b/src/fastcsi.c
new file mode 100644
index 0000000..d8983dc
--- /dev/null
+++ b/src/fastcsi.c
@@ -0,0 +1,378 @@
+#include "compat.h"
+#include "core.h"
+#include "kernels.h"
+#include "pen.h"
+#include "screen.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+TERM_ALWAYS_INLINE
+static inline const char *parse_color_sequence(const char *p, const char *end, uint32_t *color)
+{
+ const char *s = p + 5;
+ uint32_t r, g, b;
+
+ if (TERM_EXPECT(s >= end, 0)) return NULL;
+ unsigned char c0 = (unsigned char)*s++;
+ unsigned char d0 = (unsigned char)(c0 - '0');
+ if (TERM_EXPECT(d0 > 9, 0)) return NULL;
+ if (TERM_EXPECT(s >= end, 0)) return NULL;
+ unsigned char c1 = (unsigned char)*s++;
+ if (c1 == ';') {
+ r = d0;
+ } else {
+ unsigned char d1 = (unsigned char)(c1 - '0');
+ if (TERM_EXPECT(d1 > 9, 0)) return NULL;
+ if (TERM_EXPECT(s >= end, 0)) return NULL;
+ unsigned char c2 = (unsigned char)*s++;
+ if (c2 == ';') {
+ r = d0 * 10 + d1;
+ } else {
+ unsigned char d2 = (unsigned char)(c2 - '0');
+ if (TERM_EXPECT(d2 > 9 || s >= end || *s++ != ';', 0)) return NULL;
+ r = d0 * 100 + d1 * 10 + d2;
+ if (TERM_EXPECT(r > 255, 0)) return NULL;
+ }
+ }
+
+ if (TERM_EXPECT(s >= end, 0)) return NULL;
+ c0 = (unsigned char)*s++;
+ d0 = (unsigned char)(c0 - '0');
+ if (TERM_EXPECT(d0 > 9, 0)) return NULL;
+ if (TERM_EXPECT(s >= end, 0)) return NULL;
+ c1 = (unsigned char)*s++;
+ if (c1 == ';') {
+ g = d0;
+ } else {
+ unsigned char d1 = (unsigned char)(c1 - '0');
+ if (TERM_EXPECT(d1 > 9, 0)) return NULL;
+ if (TERM_EXPECT(s >= end, 0)) return NULL;
+ unsigned char c2 = (unsigned char)*s++;
+ if (c2 == ';') {
+ g = d0 * 10 + d1;
+ } else {
+ unsigned char d2 = (unsigned char)(c2 - '0');
+ if (TERM_EXPECT(d2 > 9 || s >= end || *s++ != ';', 0)) return NULL;
+ g = d0 * 100 + d1 * 10 + d2;
+ if (TERM_EXPECT(g > 255, 0)) return NULL;
+ }
+ }
+
+ if (TERM_EXPECT(s >= end, 0)) return NULL;
+ c0 = (unsigned char)*s++;
+ d0 = (unsigned char)(c0 - '0');
+ if (TERM_EXPECT(d0 > 9, 0)) return NULL;
+ if (TERM_EXPECT(s >= end, 0)) return NULL;
+ c1 = (unsigned char)*s++;
+ if (c1 == 'm') {
+ b = d0;
+ } else {
+ unsigned char d1 = (unsigned char)(c1 - '0');
+ if (TERM_EXPECT(d1 > 9, 0)) return NULL;
+ if (TERM_EXPECT(s >= end, 0)) return NULL;
+ unsigned char c2 = (unsigned char)*s++;
+ if (c2 == 'm') {
+ b = d0 * 10 + d1;
+ } else {
+ unsigned char d2 = (unsigned char)(c2 - '0');
+ if (TERM_EXPECT(d2 > 9 || s >= end || *s++ != 'm', 0)) return NULL;
+ b = d0 * 100 + d1 * 10 + d2;
+ if (TERM_EXPECT(b > 255, 0)) return NULL;
+ }
+ }
+
+ *color = term_rgb((int)r, (int)g, (int)b);
+ return s;
+}
+
+static const char *parse_256color(const char *p, const char *end, uint32_t *color)
+{
+ const char *q = p + 2;
+ unsigned v = 0;
+ int nd = 0;
+ while (q < end && (unsigned char)(*q - '0') <= 9) {
+ v = v * 10 + (unsigned char)(*q++ - '0');
+ if (++nd > 3)
+ return NULL;
+ }
+ if (!nd || q >= end || *q != 'm' || v > 255)
+ return NULL;
+ *color = term_color_index(v);
+ return q + 1;
+}
+
+TERM_NOINLINE
+int term_try_fast_csi(struct term *t, const char *buf, size_t rem)
+{
+ const char *end = buf + rem;
+ const char *p = buf;
+ const char *esc = buf;
+ const int cols = t->cols;
+ const int rows = t->rows;
+ uint32_t fg = t->cur_fg;
+ uint32_t bg = t->cur_bg;
+ uint32_t flags_pad = 0;
+ bool flags_ok = false;
+
+ while (p < end) {
+ if (p + 5 <= end && (p[0] == '3' || p[0] == '4') && p[1] == '8' &&
+ p[2] == ';' && (p[3] == '2' || p[3] == '5') && p[4] == ';') {
+ bool is_fg = p[0] == '3';
+ uint32_t *slot = is_fg ? &fg : &bg;
+ uint32_t *other = is_fg ? &bg : &fg;
+ const char *next = p[3] == '2' ? parse_color_sequence(p, end, slot)
+ : parse_256color(p + 3, end, slot);
+ if (!next) {
+ p = esc;
+ break;
+ }
+ p = next;
+ if (p + 7 <= end && p[0] == '\033' && p[1] == '[' &&
+ p[2] == (is_fg ? '4' : '3') && p[3] == '8' &&
+ p[4] == ';' && (p[5] == '2' || p[5] == '5') && p[6] == ';') {
+ next = p[5] == '2' ? parse_color_sequence(p + 2, end, other)
+ : parse_256color(p + 5, end, other);
+ if (!next) break;
+ p = next;
+ }
+ if (p < end && (unsigned char)*p >= 0x20 && (unsigned char)*p < 0x7f) {
+ char ch = *p++;
+ if (TERM_EXPECT(!flags_ok, 0)) {
+ flags_pad = term_flags_pad(t);
+ flags_ok = true;
+ }
+ if (TERM_EXPECT(!sc_put1_fast(t, ch, fg, bg, flags_pad), 0)) {
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ sc_print_ascii(t, &ch, 1);
+ }
+ if (p + 2 <= end && p[0] == '\033' && p[1] == '[') {
+ esc = p;
+ p += 2;
+ continue;
+ }
+ }
+ } else if (p[0] == 'm' || (p[0] == '0' && p + 1 < end && p[1] == 'm')) {
+ reset_sgr(t);
+ fg = t->cur_fg;
+ bg = t->cur_bg;
+ /* Recompute instead of zeroing. The cached flags carry the link
+ * id, and reset_sgr leaves cur_link set, so zeroing here would
+ * drop the hyperlink from every cell written after the reset. */
+ flags_pad = term_flags_pad(t);
+ flags_ok = true;
+ p += p[0] == 'm' ? 1 : 2;
+ } else if (p[0] == 'J' ||
+ (p[0] >= '0' && p[0] <= '3' && p + 1 < end && p[1] == 'J')) {
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ sc_erase_display(t, p[0] == 'J' ? 0 : p[0] - '0');
+ p += p[0] == 'J' ? 1 : 2;
+ } else if (p[0] == 'K' ||
+ (p[0] >= '0' && p[0] <= '2' && p + 1 < end && p[1] == 'K')) {
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ sc_erase_line(t, p[0] == 'K' ? 0 : p[0] - '0');
+ p += p[0] == 'K' ? 1 : 2;
+ } else if (!t->origin_mode && !t->lr_margin_mode &&
+ ((*p >= '0' && *p <= '9') || *p == ';' ||
+ *p == 'A' || *p == 'B' || *p == 'C' || *p == 'D' ||
+ *p == 'E' || *p == 'F' || *p == 'G' || *p == '`' ||
+ *p == 'a' || *p == 'd' || *p == 'e' ||
+ *p == 'H' || *p == 'f' || *p == 'm' ||
+ *p == 'J' || *p == 'K' || *p == 'X' ||
+ *p == 'P' || *p == '@' || *p == 's' || *p == 'u')) {
+ const char *q = p;
+ int pv[12] = { 0 };
+ int np = 0;
+ while (q < end && ((*q >= '0' && *q <= '9') || *q == ';')) {
+ if (*q == ';') {
+ if (np == 11) { q = NULL; break; }
+ np++;
+ q++;
+ continue;
+ }
+ if (pv[np] < 100000)
+ pv[np] = pv[np] * 10 + (*q - '0');
+ q++;
+ }
+ if (!q || q >= end) {
+ p = esc;
+ break;
+ }
+ int nparams = q > p ? np + 1 : 0;
+ int n = pv[0] > 0 ? pv[0] : 1;
+ int nx = t->cursor_x, ny = t->cursor_y;
+ switch (*q) {
+ case 'A': ny -= n; goto vmove;
+ case 'B': case 'e': ny += n; goto vmove;
+ case 'E': ny += n; nx = 0; goto vmove;
+ case 'F': ny -= n; nx = 0; goto vmove;
+ vmove:
+ if (TERM_EXPECT(t->scroll_top != 0 || t->scroll_bottom != rows - 1, 0)) {
+ p = esc;
+ goto done;
+ }
+ goto move;
+ case 'C': case 'a': nx += n; goto move;
+ case 'D': nx -= n; goto move;
+ case 'G': case '`': nx = n - 1; goto move;
+ case 'd': ny = n - 1; goto move;
+ case 'H': case 'f':
+ ny = n - 1;
+ nx = np && pv[1] > 0 ? pv[1] - 1 : 0;
+ goto move;
+ move:
+ sc_move(t, nx, ny);
+ p = q + 1;
+ break;
+ case 'm': {
+ for (int j = 0; j < nparams; j++) {
+ int v = pv[j];
+ switch (v) {
+ case 0: reset_sgr(t); fg = t->cur_fg; bg = t->cur_bg; break;
+ case 1: t->bold = true; break;
+ case 2: t->faint = true; break;
+ case 3: t->italic = true; break;
+ case 4: t->underline = 1; break;
+ case 7: t->inverse = true; break;
+ case 9: t->strike = true; break;
+ case 21: t->underline = 2; break;
+ case 22: t->bold = false; t->faint = false; break;
+ case 23: t->italic = false; break;
+ case 24: t->underline = 0; break;
+ case 27: t->inverse = false; break;
+ case 29: t->strike = false; break;
+ case 38: case 48: case 58: {
+ uint32_t *slot = v == 38 ? &fg : v == 48 ? &bg : &t->cur_ul;
+ if (j + 2 < nparams && pv[j + 1] == 5) {
+ if (pv[j + 2] >= 0 && pv[j + 2] <= 255)
+ *slot = term_color_index(pv[j + 2]);
+ j += 2;
+ } else if (j + 1 < nparams && pv[j + 1] == 2 &&
+ j + 4 < nparams) {
+ *slot = term_rgb(pv[j + 2], pv[j + 3], pv[j + 4]);
+ j += 4;
+ }
+ break;
+ }
+ case 39: fg = TERM_COLOR_FG; break;
+ case 49: bg = TERM_COLOR_BG; break;
+ case 53: t->overline = true; break;
+ case 55: t->overline = false; break;
+ case 59: t->cur_ul = 0; break;
+ default:
+ if ((unsigned)(v - 30) < 8) fg = term_color_index(v - 30);
+ else if ((unsigned)(v - 40) < 8) bg = term_color_index(v - 40);
+ else if ((unsigned)(v - 90) < 8) fg = term_color_index(v - 90 + 8);
+ else if ((unsigned)(v - 100) < 8) bg = term_color_index(v - 100 + 8);
+ break;
+ }
+ }
+ flags_ok = false;
+ p = q + 1;
+ break;
+ }
+ case 'J':
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ sc_erase_display(t, pv[0]);
+ p = q + 1;
+ break;
+ case 'K':
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ sc_erase_line(t, pv[0]);
+ p = q + 1;
+ break;
+ case 'X':
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ sc_erase_span(t, t->cursor_y, t->cursor_x,
+ n > cols - t->cursor_x ? cols : t->cursor_x + n);
+ p = q + 1;
+ break;
+ case 'P':
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ sc_delete_chars(t, n);
+ p = q + 1;
+ break;
+ case '@':
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ sc_insert_chars(t, n);
+ p = q + 1;
+ break;
+ case 's':
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ t->saved = term_cursor_save(t);
+ p = q + 1;
+ break;
+ case 'u':
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ sc_restore_cursor(t, &t->saved);
+ fg = t->cur_fg;
+ bg = t->cur_bg;
+ flags_ok = false;
+ p = q + 1;
+ if (t->origin_mode || t->graphics)
+ goto done;
+ break;
+ default:
+ p = esc;
+ goto done;
+ }
+ } else {
+ p = esc;
+ break;
+ }
+tail:
+ if (p < end && (unsigned char)(*p - 0x20) < 0x5f && !t->pending_wrap) {
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ int avail = cols - t->cursor_x;
+ int limit = end - p < (ptrdiff_t)avail ? (int)(end - p) : avail;
+ int run = scan_ascii_run(p, limit);
+ if (TERM_EXPECT(!flags_ok, 0)) {
+ flags_pad = term_flags_pad(t);
+ flags_ok = true;
+ }
+ if (!sc_ascii_fast(t, p, run, fg, bg, flags_pad))
+ sc_print_ascii(t, p, run);
+ p += run;
+ }
+ if (p + 2 <= end && p[0] == '\033' && p[1] == '[') {
+ esc = p;
+ p += 2;
+ continue;
+ }
+ if (p < end && *p == '\r') {
+ sc_cr(t);
+ p++;
+ goto tail;
+ }
+ if (p < end && *p == '\n') {
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ if (term_lnm(t)) sc_cr(t);
+ sc_linefeed(t, 1);
+ p++;
+ goto tail;
+ }
+ break;
+ }
+done:
+ if (p == end && esc != buf && esc == p - 2)
+ p = esc;
+ t->cur_fg = fg;
+ t->cur_bg = bg;
+ if (p > buf)
+ return (int)(p - buf);
+ return 0;
+}