diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 17:49:34 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 17:49:34 +0300 |
| commit | 4384435941b8c7658856b381d071aee2e0035e89 (patch) | |
| tree | 5ece99c3dd1decb8ec3fd404c0ece2b5865a552a /src/text.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/text.c')
| -rw-r--r-- | src/text.c | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/src/text.c b/src/text.c new file mode 100644 index 0000000..9f73bd3 --- /dev/null +++ b/src/text.c @@ -0,0 +1,350 @@ +#include "compat.h" +#include "core.h" +#include "kernels.h" +#include "text_run.h" +#include "screen.h" +#include "escape.h" +#include "palette.h" +#include "unicode/grapheme.h" +#include "unicode/unicode.h" +#include "text.h" +#include "unicode/utf8.h" +#include <stdio.h> +#include <string.h> + +static void dispatch_c1_control(struct term *t, uint32_t cp) +{ + static const char c1_map[32] = { + [4] = 'D', [5] = 'E', [8] = 'H', [13] = 'M', + [16] = 'P', [27] = '[', [28] = '\\', [29] = ']' }; + char ch = c1_map[cp - 0x80]; + if (!ch) return; + t->state = ST_ESC; + term_escape_byte(t, (unsigned char)ch); +} + +static inline void emit_codepoint(struct term *t, uint32_t cp, int klass) +{ + static const uint32_t dec_graphics[] = { + 0x00a0, + 0x25c6,0x2592,0x2409,0x240c,0x240d,0x240a,0x00b0,0x00b1, + 0x2424,0x240b,0x2518,0x2510,0x250c,0x2514,0x253c,0x23ba, + 0x23bb,0x2500,0x23bc,0x23bd,0x251c,0x2524,0x2534,0x252c, + 0x2502,0x2264,0x2265,0x03c0,0x2260,0x00a3,0x00b7 }; + if (cp >= 0x80 && cp <= 0x9f) { + dispatch_c1_control(t, cp); + return; + } + if (t->graphics && cp >= '_' && cp <= '~') { + cp = dec_graphics[cp - '_']; + klass = -1; + } + term_sc_print_glyph(t, cp, klass, term_current_flags(t)); +} + +static inline void emit_decoded(struct term *t, uint32_t cp) +{ + int klass = term_text_class(cp); + if (!term_sc_extend_cluster(t, cp, klass)) + emit_codepoint(t, cp, klass); +} + +/* A byte that cannot continue a UTF-8 sequence ends it: the unfinished + * sequence prints U+FFFD (Unicode 3.9, maximal subparts). */ +void term_text_utf8_interrupt(struct term *t) +{ + if (TERM_EXPECT(t->utf8_state == UTF8_ACCEPT, 1)) + return; + t->utf8_state = UTF8_ACCEPT; + emit_decoded(t, 0xFFFD); +} + +void term_text_ground(struct term *t, unsigned char ch) +{ + if (ch >= 0x20 && ch < 0x7f && t->utf8_state == UTF8_ACCEPT) { + emit_codepoint(t, ch, 1); + return; + } + + if (ch < 0x20 || ch == 0x7f) + term_text_utf8_interrupt(t); + + if (TERM_EXPECT(ch == 0x1b, 0)) { + term_escape_begin(t); + } else if (ch == '\n' || ch == 0x0b || ch == 0x0c) { + /* VT and FF act as LF (xterm). */ + if (term_lnm(t)) sc_cr(t); + sc_linefeed(t, 1); + } else if (ch == '\r') { + sc_cr(t); + } else if (ch == '\b') { + sc_bs(t); + } else if (ch == '\t') { + term_sc_tab(t, 1); + } else if (ch == 0x07) { + if (t->cb.bell) + t->cb.bell(t->cb.ud); + } else if (ch == 0x0e) { + t->charset_active = 1; + t->graphics = t->charsets[1] == '0'; + } else if (ch == 0x0f) { + t->charset_active = 0; + t->graphics = t->charsets[0] == '0'; + } else if (ch >= 0x20 && ch != 0x7f) { + uint32_t before = t->utf8_state; + uint32_t st = utf8_decode(&t->utf8_state, &t->utf8_codep, ch); + if (st == UTF8_ACCEPT) { + emit_decoded(t, t->utf8_codep); + } else if (st == UTF8_REJECT) { + t->utf8_state = UTF8_ACCEPT; + emit_decoded(t, 0xFFFD); + /* The byte that ended an unfinished sequence starts anew. */ + if (before != UTF8_ACCEPT) + term_text_ground(t, ch); + } + } +} + +static inline int decode_ground_utf8(struct term *t, const char *buf, size_t len, + uint32_t *cp) +{ + return utf8_decode_ground(&t->utf8_state, &t->utf8_codep, buf, len, cp); +} + +static inline bool can_stream_bottom(const struct term *t) +{ + return t->state == ST_GROUND && !t->graphics && !t->insert_mode && t->autowrap && + !t->lr_margin_mode && + t->utf8_state == UTF8_ACCEPT && !t->inverse && + t->scroll_top == 0 && t->scroll_bottom == t->rows - 1 && + t->cursor_y == t->rows - 1 && + !t->alternate && t->sb_capacity > 0 && + using_default_colors(t); +} + +static inline bool try_text_run(struct term *t, const char *buf, size_t len, + size_t *idx) +{ + size_t i = *idx; + if (t->pending_wrap || i + 4 > len || + ((unsigned char)buf[i] >= 0x80 && + ((unsigned char)buf[i] < 0xc2 || (unsigned char)buf[i] > 0xf4)) || + t->last_char == 0x200D || grapheme_is_handle(t->last_char)) + return false; + if ((unsigned char)buf[i] >= 0x80) { + uint32_t cp; + int bytes = utf8_decode_one(buf + i, len - i, &cp); + if (!bytes || term_text_class(cp) <= 0) + return false; + } + if (t->cursor_x != 0 && + t->cursor_x != term_row(t, t->cursor_y)->len) + return false; + uint32_t fg = t->inverse ? t->cur_bg : t->cur_fg; + uint32_t bg = t->inverse ? t->cur_fg : t->cur_bg; + uint32_t flags = term_flags_pad(t); + bool wrote = false; + do { + struct srow *text = t->insert_mode || t->lr_margin_mode ? NULL : text_append_row(t); + if (text) { + int x = t->cursor_x, avail = t->cols - x; + struct text_run_bytes r = term_text_run_bytes(text->text.text + text->text.text_len, + buf + i, (int)(len - i), avail, + t->last_char, &t->text_width); + if (!r.bytes) + break; + wrote = true; + text_commit(text, r.out, r.chars, r.cols, + (uint8_t)((r.narrow ? HLINE_NARROW : 0) | (r.wide ? HLINE_HAS_WIDE : 0)), + fg, bg, flags); + t->last_char = r.last; + sc_text_advance(t, x, r.cols, avail); + i += (size_t)r.bytes; + } else { + struct span span = sc_span_begin(t, t->cols); + if (span.avail < 2) + break; + struct text_run_result r = term_text_run(span.cells, buf + i, (int)(len - i), + span.avail, fg, bg, flags, t->last_char, + &t->text_width); + if (!r.bytes) + break; + wrote = true; + sc_span_end(t, &span, r.cols, 0, r.last); + i += (size_t)r.bytes; + } + bool newline = false; + if (i < len && buf[i] == '\r') { + sc_cr(t); + i++; + if (i < len && buf[i] == '\n') { + sc_linefeed(t, 1); + i++; + newline = true; + } + } else if (i < len && buf[i] == '\n') { + if (term_lnm(t)) sc_cr(t); + sc_linefeed(t, 1); + i++; + newline = true; + } + if (!newline || i + 4 > len || t->pending_wrap || + t->last_char == 0x200D || grapheme_is_handle(t->last_char) || + (t->cursor_x != 0 && + t->cursor_x != term_row(t, t->cursor_y)->len)) + break; + } while (true); + if (wrote) *idx = i - 1; + return wrote; +} + +static void term_feed_inner(struct term *t, const char *buf, size_t len, unsigned flags) +{ + if (!t || !buf || len == 0) + return; + t->transport_newline = (flags & TERM_FEED_ONLCR) != 0; + + for (size_t i = 0; i < len; i++) { + while (t->state != ST_GROUND) { + if (t->state == ST_CSI) + i += term_escape_csi(t, buf + i, len - i); + else if (t->state == ST_OSC || t->state == ST_DCS || t->state == ST_IGNORE) + i += term_escape_str(t, buf + i, len - i); + else + term_escape_byte(t, (unsigned char)buf[i++]); + if (i == len) return; + } + + unsigned char ch = (unsigned char)buf[i]; + if ((ch < 0x20 || ch == 0x7f) && t->state == ST_GROUND) + term_text_utf8_interrupt(t); + + if (TERM_EXPECT(ch < 0x20 && t->state == ST_GROUND && + ch != 0x1b && ch != '\n' && ch != '\r' && ch != '\b' && ch != '\t' && + ch != 0x0e && ch != 0x0f && ch != 0x07 && ch != 0x0b && + ch != 0x0c, 0)) { + continue; + } + + if (TERM_EXPECT(ch == 0x1b && t->state == ST_GROUND && + t->utf8_state == UTF8_ACCEPT && i + 1 < len && + buf[i + 1] == '[' && !t->insert_mode && + !t->lr_margin_mode && !t->graphics, 0)) { + int fast = term_try_fast_csi(t, buf + i + 2, len - i - 2); + if (fast > 0) { + i += 1 + (size_t)fast; + continue; + } + t->state = ST_CSI; + csi_parser_reset(&t->csi_parser); + i += 1 + term_escape_csi(t, buf + i + 2, len - i - 2); + continue; + } + + if (TERM_EXPECT(t->state == ST_GROUND && ch == '\n', 0)) { + size_t end = i + 1 + scan_byte_run(buf + i + 1, len - i - 1, '\n'); + if (term_lnm(t)) sc_cr(t); + sc_linefeed(t, end - i); + i = end - 1; + continue; + } + + if (TERM_EXPECT(t->state == ST_GROUND && ch == '\r' && + i + 1 < len && buf[i + 1] == '\n', 0)) { + size_t end = i + 2 + scan_crlf_pairs(buf + i + 2, len - i - 2); + sc_cr(t); + sc_linefeed(t, (end - i) / 2); + i = end - 1; + continue; + } + + if (TERM_EXPECT(ch >= 0x20 && ch < 0x7f && !t->insert_mode && !t->lr_margin_mode && + t->state == ST_GROUND && !t->graphics && + t->utf8_state == UTF8_ACCEPT, 1)) { + int avail = t->pending_wrap ? t->cols : t->cols - t->cursor_x; + int rem = (int)(len - i); + if (rem > avail) rem = avail; + int run = 1; + if (rem > 1 && (unsigned char)buf[i + 1] >= 0x20 && + (unsigned char)buf[i + 1] < 0x7f) + run += scan_ascii_run(buf + i + 1, rem - 1); + if (TERM_EXPECT(can_stream_bottom(t), 1) && + (run == t->cols || + (i + (size_t)run < len && + (buf[i + run] == '\r' || buf[i + run] == '\n')))) { + struct srow *row = term_row(t, t->cursor_y); + if (t->pending_wrap || + (t->cursor_x == 0 && row && row->len == 0)) { + size_t done = term_sc_bulk_ascii_lines(t, buf + i, len - i); + if (done > 0) { + i += done - 1; + continue; + } + } + } + if (i + (size_t)run < len && (unsigned char)buf[i + run] >= 0x80) { + uint32_t next_cp; + if (utf8_decode_one(buf + i + run, len - i - (size_t)run, &next_cp) && + term_text_class(next_cp) > 0 && + try_text_run(t, buf, len, &i)) + continue; + } + sc_print_ascii(t, buf + i, run); + i += (size_t)run; + if (i < len && buf[i] == '\r') { + sc_cr(t); + i++; + if (i < len && buf[i] == '\n') { + sc_linefeed(t, 1); + i++; + } + } else if (i < len && buf[i] == '\n') { + if (term_lnm(t)) sc_cr(t); + sc_linefeed(t, 1); + i++; + } + i--; + continue; + } + + if (TERM_EXPECT(ch == '\r' && t->state == ST_GROUND, 0)) { + sc_cr(t); + continue; + } + + if (TERM_EXPECT(ch >= 0x80 && t->state == ST_GROUND && + t->utf8_state == UTF8_ACCEPT && !t->graphics, 1)) { + if (!t->insert_mode && !t->lr_margin_mode && + try_text_run(t, buf, len, &i)) + continue; + uint32_t cp; + int cp_len = decode_ground_utf8(t, buf + i, len - i, &cp); + if (cp) + emit_decoded(t, cp); + i += (size_t)(cp_len - 1); + continue; + } + + if (TERM_EXPECT(t->state == ST_GROUND, 1)) + term_text_ground(t, ch); + else + term_escape_byte(t, ch); + } +} + +void term_feed(struct term *t, const char *buf, size_t len, unsigned flags) +{ + if (!t || !buf || len == 0) + return; + term_write_begin(t); + term_feed_inner(t, buf, len, flags); + term_write_end(t); +} + +void term_text_repeat(struct term *t, int count) +{ + int screen = t->cols * t->rows; + if (count > screen) count = screen; + for (int j = 0; j < count && t->last_char; j++) + term_sc_print_glyph(t, t->last_char, -1, term_current_flags(t)); +} |
