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/core.h | |
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/core.h')
| -rw-r--r-- | src/core.h | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/src/core.h b/src/core.h new file mode 100644 index 0000000..2460938 --- /dev/null +++ b/src/core.h @@ -0,0 +1,385 @@ +#ifndef WSAP_TERM_CORE_H +#define WSAP_TERM_CORE_H + +#include "compat.h" +#include "internal.h" +#include "screen.h" + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +struct span { + struct srow *row; + struct cell *cells; + int avail; + int x; + int old_len; +}; + +/* Begin writing a span of cells. Settle cursor row, repair left edge. */ +static inline struct span sc_span_begin(struct term *t, int want); + +/* Finish writing a span of cells. Repair right edge, raise len, bump gen. */ +static inline void sc_span_end(struct term *t, struct span *s, int cols, + uint8_t row_flags, uint32_t last); + +/* Print ASCII characters through span and SIMD chunking. */ +static inline void sc_print_ascii(struct term *t, const char *s, int n); + +/* Print one glyph (single codepoint or cluster handle). */ +void term_sc_print_glyph(struct term *t, uint32_t cp, int width, uint16_t flags); + +/* Extend the last printed glyph into a grapheme cluster. */ +bool term_sc_extend_cluster(struct term *t, uint32_t cp, int klass); + +/* Multi-line or region-constrained line feed helper. */ +void term_sc_linefeed_slow(struct term *t, size_t n); + +/* Line feed: move down or scroll with BCE. */ +static inline void sc_linefeed(struct term *t, size_t n) +{ + if (TERM_EXPECT(n == 1 && t->scroll_top == 0 && + t->scroll_bottom == t->rows - 1, 1)) { + t->pending_wrap = false; + term_row(t, t->cursor_y)->version++; + if (TERM_EXPECT(t->cursor_y == t->rows - 1, 1)) + term_screen_scroll_up(t); + else + t->cursor_y++; + return; + } + term_sc_linefeed_slow(t, n); +} + +/* Carriage return: move cursor to left margin. */ +static inline void sc_cr(struct term *t) +{ + t->pending_wrap = false; + if (t->cursor_x != 0) { + t->cursor_x = 0; + term_row(t, t->cursor_y)->version++; + } +} + +/* Move cursor to (x, y), margin-aware. */ +static inline void sc_move(struct term *t, int x, int y) +{ + t->pending_wrap = false; + if (x < 0) x = 0; + if (x >= t->cols) x = t->cols - 1; + if (y < 0) y = 0; + if (y >= t->rows) y = t->rows - 1; + + int old_y = t->cursor_y; + if (x == t->cursor_x && y == old_y) + return; + t->cursor_x = x; + t->cursor_y = y; + term_row(t, old_y)->version++; + if (y != old_y) + term_row(t, y)->version++; +} + +/* Advance cursor by n tab stops. */ +void term_sc_tab(struct term *t, int n); + +/* Move cursor back one column. */ +static inline void sc_bs(struct term *t) +{ + t->pending_wrap = false; + if (t->cursor_x > 0) + t->cursor_x--; + term_row(t, t->cursor_y)->version++; +} + +/* Erase cells in span [from, to) on physical row y. */ +static inline void sc_erase_span(struct term *t, int y, int from, int to) +{ + t->pending_wrap = false; + if (to > t->cols) to = t->cols; + int p = phys_row(t, y); + erase_span(t, p, from, to); + t->scr.slot[p].version++; +} + +/* Erase display mode 0, 1, 2, or 3. */ +static inline void sc_erase_display(struct term *t, int mode) +{ + t->pending_wrap = false; + term_screen_erase(t, mode); +} + +/* Erase line mode 0, 1, or 2. */ +static inline void sc_erase_line(struct term *t, int mode) +{ + t->pending_wrap = false; + term_screen_erase_line(t, mode); +} + +/* Insert n blank characters at cursor. */ +static inline void sc_insert_chars(struct term *t, int n) +{ + t->pending_wrap = false; + term_screen_insert_chars(t, n); +} + +/* Delete n characters at cursor. */ +static inline void sc_delete_chars(struct term *t, int n) +{ + t->pending_wrap = false; + term_screen_delete_chars(t, n); +} + +/* Scroll margins: top to bottom, n lines up or down. */ +void term_sc_scroll_region(struct term *t, int top, int bottom, int n, bool down); + +/* Scroll the active scroll region up or down by n lines (with history). */ +void term_sc_scroll(struct term *t, int n, bool down); + +/* Restore saved cursor state and position. */ +static inline void sc_restore_cursor(struct term *t, const struct term_saved_cursor *s) +{ + term_cursor_apply(t, s); + sc_move(t, s->x, s->y); + t->pending_wrap = s->pending_wrap; +} + +/* Change attributes in a rectangular or stream area (DECCARA). */ +void term_sc_deccara(struct term *t, const struct csi *csi); + +/* Screen alignment pattern (DECALN). */ +void term_sc_decaln(struct term *t); + +/* Reset screen and cursor state for full reset (RIS). */ +void term_sc_reset(struct term *t); + +/* Set reverse video mode (DECSCNM). */ +void term_sc_set_reverse_video(struct term *t, bool enabled); + +/* Set autowrap mode (DECAWM). */ +void term_sc_set_autowrap(struct term *t, bool enabled); + +/* Set cursor style (DECSCUSR). */ +void term_sc_set_cursor_style(struct term *t, int ps); + +/* Bulk streaming of ASCII lines with raw rows and skip-ahead. */ +size_t term_sc_bulk_ascii_lines(struct term *t, const char *buf, size_t len); + +/* Fast CSI parser. */ +int term_try_fast_csi(struct term *t, const char *buf, size_t rem); + +static inline struct span sc_span_begin(struct term *t, int want) +{ + (void)want; + if (TERM_EXPECT(t->pending_wrap, 0)) { + row_set_wrapped(term_row(t, t->cursor_y), true); + t->cursor_x = t->lr_margin_mode ? t->left_margin : 0; + sc_linefeed(t, 1); + t->pending_wrap = false; + } + struct srow *row = term_row(t, t->cursor_y); + if (TERM_EXPECT(row->kind != ROW_CELLS, 0)) + srow_settle(row, t->cols); + int cx = t->cursor_x; + term_row_fill_gap(row, cx); + int len = row->len; + if (TERM_EXPECT(cx < len && (row->cells[cx].flags & CELL_TRAIL), 0)) + fill_cells(row->cells + cx - 1, 1, row->cells[cx - 1].fg, row->cells[cx - 1].bg); + + int right = TERM_EXPECT(t->lr_margin_mode, 0) ? t->right_margin : t->cols - 1; + int avail = right + 1 > cx ? right + 1 - cx : 0; + return (struct span){ + .row = row, + .cells = row->cells + cx, + .avail = avail, + .x = cx, + .old_len = len, + }; +} + +static inline void sc_span_end(struct term *t, struct span *s, int cols, + uint8_t row_flags, uint32_t last) +{ + struct srow *row = s->row; + int end_x = s->x + cols; + if (TERM_EXPECT(end_x < s->old_len && (row->cells[end_x].flags & CELL_TRAIL), 0)) + fill_cells(row->cells + end_x, 1, row->cells[end_x].fg, row->cells[end_x].bg); + if (end_x > s->old_len) + row->len = (uint16_t)end_x; + if (TERM_EXPECT(row_flags != 0, 0)) + row->flags |= row_flags; + row->version++; + t->last_char = last; + + if (TERM_EXPECT(cols < s->avail, 1)) { + t->cursor_x = end_x; + t->pending_wrap = false; + } else { + t->cursor_x = s->x + s->avail - 1; + t->pending_wrap = t->autowrap; + } +} + +/* Move the cursor after cols columns written at x, of avail. */ +static inline void sc_text_advance(struct term *t, int x, int cols, int avail) +{ + if (cols < avail) { + t->cursor_x = x + cols; + t->pending_wrap = false; + } else { + t->cursor_x = x + avail - 1; + t->pending_wrap = t->autowrap; + } +} + +/* Append ASCII at the cursor, with the pen fg, bg, sl (flags | link << 16), + * when the cursor row takes text. Return the bytes taken. */ +static inline int sc_text_ascii(struct term *t, const char *s, int n, uint32_t fg, uint32_t bg, + uint32_t sl) +{ + if (TERM_EXPECT(t->insert_mode || t->lr_margin_mode, 0)) + return 0; + if (t->pending_wrap) { + row_set_wrapped(term_row(t, t->cursor_y), true); + t->cursor_x = 0; + sc_linefeed(t, 1); + t->pending_wrap = false; + } + struct srow *row = text_append_row(t); + if (!row) + return 0; + int x = t->cursor_x, avail = t->cols - x; + int take = n < avail ? n : avail; + if (take <= 0) + return 0; + text_append(row, (const uint8_t *)s, take, take, take, HLINE_NARROW, fg, bg, sl); + t->last_char = (unsigned char)s[take - 1]; + sc_text_advance(t, x, take, avail); + return take; +} + +static inline void sc_print_ascii(struct term *t, const char *s, int n) +{ + uint32_t fg = TERM_EXPECT(t->inverse, 0) ? t->cur_bg : t->cur_fg; + uint32_t bg = TERM_EXPECT(t->inverse, 0) ? t->cur_fg : t->cur_bg; + uint32_t flags = term_flags_pad(t); + + for (int k; n > 0 && (k = sc_text_ascii(t, s, n, fg, bg, flags)) > 0; s += k, n -= k) {} + + while (n > 0) { + struct span span = sc_span_begin(t, n); + int take = n < span.avail ? n : span.avail; + if (TERM_EXPECT(take <= 0, 0)) { + t->pending_wrap = t->autowrap; + if (!t->autowrap) + break; + span = sc_span_begin(t, n); + take = n < span.avail ? n : span.avail; + if (take <= 0) + break; + } + put_ascii_chunk(span.cells, s, take, fg, bg, flags); + sc_span_end(t, &span, take, 0, (unsigned char)s[take - 1]); + if (TERM_EXPECT(n <= take, 1)) break; + s += take; + n -= take; + } +} + +/* Write a short ASCII span without settling an already writable row. */ +static inline bool sc_ascii_fast(struct term *t, const char *s, int n, + uint32_t fg, uint32_t bg, uint32_t flags_pad) +{ + int cx = t->cursor_x; + if (TERM_EXPECT(t->pending_wrap || t->insert_mode || + t->lr_margin_mode || t->inverse || + n <= 0 || n > t->cols - cx, 0)) + return false; + struct srow *row = term_row(t, t->cursor_y); + if (row->kind == ROW_TEXT || (row->len == 0 && cx == 0)) { + if (sc_text_ascii(t, s, n, fg, bg, flags_pad) == n) + return true; + if (row->kind == ROW_TEXT) + return false; + } + if (TERM_EXPECT(row->kind != ROW_CELLS || cx > row->len, 0)) + return false; + int len = row->len; + struct cell *cells = row->cells; + if (TERM_EXPECT((cx < len && (cells[cx].flags & CELL_TRAIL)) || + (cx + n < len && (cells[cx + n].flags & CELL_TRAIL)), 0)) + return false; + put_ascii_chunk(cells + cx, s, n, fg, bg, flags_pad); + cx += n; + if (cx > len) + row->len = (uint16_t)cx; + row->version++; + t->last_char = (unsigned char)s[n - 1]; + if (cx < t->cols) { + t->cursor_x = cx; + t->pending_wrap = false; + } else { + t->cursor_x = t->cols - 1; + t->pending_wrap = t->autowrap; + } + return true; +} + +/* Fast inline check for 1-character write (fg-char fast path). */ +static inline bool sc_put1_fast(struct term *t, char ch, uint32_t fg, uint32_t bg, + uint32_t flags_pad) +{ + int cx = t->cursor_x; + int cols = t->cols; + if (TERM_EXPECT(t->pending_wrap | t->insert_mode | t->lr_margin_mode | t->inverse, 0)) + return false; + if (TERM_EXPECT(cx >= cols, 0)) + return false; + struct srow *row = term_row(t, t->cursor_y); + if (row->kind == ROW_TEXT) { + struct text_row *tr = &row->text; + if (TERM_EXPECT(cx != tr->cells || !tr->cells || cx + 1 == cols, 0)) + return sc_text_ascii(t, &ch, 1, fg, bg, flags_pad) == 1; + /* One character in the middle of a text row. */ + tr->text[tr->text_len++] = (uint8_t)ch; + tr->cells++; + hist_runs_add_pen(&tr->runs, fg, bg, flags_pad, 1); + if (flags_pad >> 16) + tr->flags |= HLINE_HAS_LINK; + if (tr->cells > row->len) + row->len = tr->cells; + row->version++; + t->last_char = (uint8_t)ch; + t->cursor_x = cx + 1; + return true; + } + if (row->len == 0 && cx == 0) { + if (sc_text_ascii(t, &ch, 1, fg, bg, flags_pad) == 1) + return true; + } + int len = row->len; + if (TERM_EXPECT(cx > len, 0)) + return false; + struct cell *cells = row->cells; + if (TERM_EXPECT((cx < len && (cells[cx].flags & CELL_TRAIL)) || + (cx + 1 < len && (cells[cx + 1].flags & CELL_TRAIL)), 0)) + return false; + + cells[cx] = (struct cell){ (uint8_t)ch, fg, bg, + (uint16_t)flags_pad, (uint16_t)(flags_pad >> 16) }; + t->last_char = (uint8_t)ch; + cx++; + if (cx > len) + row->len = (uint16_t)cx; + row->version++; + if (TERM_EXPECT(cx < cols, 1)) { + t->cursor_x = cx; + } else { + t->cursor_x = cols - 1; + t->pending_wrap = t->autowrap; + } + return true; +} + +#endif |
