summaryrefslogtreecommitdiff
path: root/src/internal.h
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/internal.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/internal.h')
-rw-r--r--src/internal.h411
1 files changed, 411 insertions, 0 deletions
diff --git a/src/internal.h b/src/internal.h
new file mode 100644
index 0000000..dd4649c
--- /dev/null
+++ b/src/internal.h
@@ -0,0 +1,411 @@
+#ifndef TERM_INTERNAL_H
+#define TERM_INTERNAL_H
+
+#include "term.h"
+#include "cell.h"
+#include "csi.h"
+#include "gstore.h"
+#include "hist.h"
+#include "hist_view.h"
+#include "palette.h"
+#include "pen.h"
+#include "text_run.h"
+#include "compat.h"
+#include <stdatomic.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define HASH64_BASIS UINT64_C(0x9E3779B97F4A7C15)
+#define KNUTH_HASH_MULT UINT32_C(2654435761)
+
+static inline uint64_t hash64_step(uint64_t hash, uint32_t value)
+{
+ hash ^= value;
+ hash *= HASH64_BASIS;
+ return hash ^ (hash >> 29);
+}
+
+static inline uint64_t hash64_finish(uint64_t hash)
+{
+ hash ^= hash >> 32;
+ hash *= UINT64_C(0xD6E8FEB86659FD93);
+ hash ^= hash >> 32;
+ return hash;
+}
+
+#define KEYBOARD_STACK_SIZE 32
+
+struct keyboard_mode {
+ unsigned flags, saved[KEYBOARD_STACK_SIZE], depth;
+};
+
+/* What DECSC saves, as in xterm: the cursor, the pen, origin mode, the
+ * pending wrap, and the character sets. */
+struct term_saved_cursor {
+ int x, y;
+ uint32_t fg, bg, ul;
+ char charsets[2];
+ uint8_t underline;
+ uint8_t charset_active;
+ bool bold : 1;
+ bool faint : 1;
+ bool italic : 1;
+ bool strike : 1;
+ bool overline : 1;
+ bool inverse : 1;
+ bool origin : 1;
+ bool pending_wrap : 1;
+};
+
+/*
+ * A row stores cells, text, or default-pen ASCII. A text row uses the history
+ * format. An ASCII row stores only its pointer and length. A screen read or
+ * edit sets its text fields first. Other changes settle text into cells.
+ */
+enum { ROW_CELLS, ROW_TEXT, ROW_ASCII };
+enum { RF_WRAPPED = 1, RF_WIDE = 4, RF_CLUSTER = 8, RF_TAIL_PADDED = 16 };
+
+struct srow {
+ struct cell *cells; /* stride >= cols, from a per-screen slab */
+ uint8_t *ascii;
+ struct text_row text; /* TEXT: the content; len == text.cells, or cols with a wrap pad */
+ uint64_t uid; /* new id when the row scrolls in or clears */
+ uint32_t version; /* bumped on every change */
+ uint16_t len;
+ uint8_t kind, flags;
+};
+
+struct screen {
+ struct srow *slot;
+ struct cell *cells_slab;
+ uint8_t *text_slab;
+ size_t cells_cap, text_cap;
+ /* Without history: the line of row 0, counted as rows leave the top. */
+ uint64_t top_id;
+ int rows, cols, top;
+ int stride;
+ /* Capacity of the buffers above. A resize reuses them. */
+ int slot_cap;
+ uint32_t top_seg;
+};
+
+struct term_sel_point {
+ uint64_t line_id;
+ uint32_t cell_off;
+};
+
+/* A buffer that only grows, so work that repeats does not allocate. */
+struct grow_buf {
+ void *data;
+ size_t cap;
+};
+
+/* What a resize needs besides the screens. It keeps its buffers, so a
+ * resize allocates nothing once the terminal has seen its sizes. */
+struct resize_scratch {
+ struct grow_buf row, rows, cells, line, pull, rest, lens;
+};
+
+
+struct term {
+ _Atomic uint64_t seq;
+ _Atomic uint64_t writer_tid;
+ uint32_t write_depth;
+ int cols;
+ int rows;
+ int cursor_x;
+ int cursor_y;
+ int scroll_top, scroll_bottom;
+
+ uint32_t cur_fg;
+ uint32_t cur_bg;
+ uint32_t cur_ul;
+ uint32_t last_char;
+
+ uint32_t utf8_state;
+ uint32_t utf8_codep;
+ struct width_cache text_width;
+
+ enum { ST_GROUND, ST_ESC, ST_CSI, ST_OSC, ST_DCS, ST_STR_ESC, ST_CHARSET,
+ ST_DCS_ESC, ST_IGNORE, ST_IGNORE_ESC, ST_DOCS, ST_DECALN } state;
+ struct csi_parser csi_parser;
+
+ uint16_t cur_link;
+ char charsets[2];
+ uint8_t underline;
+ uint8_t decsace;
+ char charset_designation;
+ bool graphics;
+
+ bool bold : 1;
+ bool faint : 1;
+ bool italic : 1;
+ bool strike : 1;
+ bool overline : 1;
+ bool inverse : 1;
+ bool pending_wrap : 1;
+ bool autowrap : 1;
+ bool insert_mode : 1;
+ bool origin_mode : 1;
+ bool reverse_video : 1;
+ bool synchronized_output : 1;
+ bool alternate : 1;
+ bool application_cursor : 1;
+ bool application_keypad : 1;
+ bool lr_margin_mode : 1;
+ bool backarrow_sends_bs : 1;
+ bool focus_events : 1;
+ bool cursor_visible : 1;
+ bool cursor_blink : 1;
+ bool default_cursor_blink : 1;
+ bool selection : 1;
+ bool bracketed_paste : 1;
+ bool lnm : 1;
+ bool transport_newline : 1;
+ bool str_truncated : 1;
+ bool has_prompt : 1;
+
+ struct screen scr;
+ struct screen alt_scr;
+ struct screen spare; /* the buffers a resize builds into */
+ struct resize_scratch resize;
+ struct grow_buf ascii_ends;
+ struct grow_buf ascii_tail;
+ struct term_saved_cursor normal_saved_cursor;
+ struct term_saved_cursor normal_saved_decsc;
+ int normal_cols, normal_rows;
+ bool alt_allocated;
+ bool normal_cursor_visible;
+ uint8_t normal_cursor_style;
+ bool normal_cursor_blink;
+
+ struct hist hist;
+ struct hist_view view;
+ uint64_t next_row_uid;
+ uint64_t full_gen;
+ int prompt_y;
+ int left_margin, right_margin;
+ struct term_sel_point sel_start, sel_end;
+ struct term_sel_point sel_anchor_first, sel_anchor_last;
+ enum term_select_unit select_unit;
+
+ int mouse_mode, mouse_encoding;
+ enum term_cursor_style cursor_style;
+ enum term_cursor_style default_cursor_style;
+ int charset_active;
+ int osc_len;
+ int dcs_len;
+ int sb_capacity;
+
+ struct keyboard_mode keyboard[2];
+ /* The DECSC slot. Cold: it stays out of the hot fields above. */
+ struct term_saved_cursor saved;
+
+ uint8_t *tabs;
+ struct cell *view_row_scratch;
+ int tabs_cap;
+ int view_scratch_cap;
+
+ char osc_buf[256];
+ char tcap_name[64];
+
+ char *osc_heap;
+ size_t osc_heap_cap;
+ char *dcs_buf;
+ size_t dcs_cap;
+ void *links;
+ char *cwd;
+ char *pointer_shape;
+
+ struct term_callbacks cb;
+ struct palette palette;
+ struct gstore *gstore;
+ /* Unique for the life of the process. A snapshot compares it, not the
+ * address, because a new terminal can reuse a freed one's memory. */
+ uint64_t serial;
+};
+
+static inline void reset_sgr(struct term *t)
+{
+ t->cur_fg = TERM_COLOR_FG;
+ t->cur_bg = TERM_COLOR_BG;
+ t->bold = false;
+ t->faint = false;
+ t->italic = false;
+ t->underline = 0;
+ t->strike = false;
+ t->overline = false;
+ t->inverse = false;
+ t->cur_ul = 0;
+}
+
+/* The DECSC slot before any save: home, the default pen, and ASCII. */
+static inline struct term_saved_cursor term_saved_cursor_initial(void)
+{
+ return (struct term_saved_cursor){
+ .fg = TERM_COLOR_FG, .bg = TERM_COLOR_BG, .charsets = { 'B', 'B' },
+ };
+}
+
+static inline struct term_saved_cursor term_cursor_save(const struct term *t)
+{
+ return (struct term_saved_cursor){
+ .x = t->cursor_x, .y = t->cursor_y,
+ .fg = t->cur_fg, .bg = t->cur_bg, .ul = t->cur_ul,
+ .charsets = { t->charsets[0], t->charsets[1] },
+ .underline = t->underline,
+ .charset_active = (uint8_t)t->charset_active,
+ .bold = t->bold, .faint = t->faint, .italic = t->italic,
+ .strike = t->strike, .overline = t->overline, .inverse = t->inverse,
+ .origin = t->origin_mode, .pending_wrap = t->pending_wrap,
+ };
+}
+
+/* Take the pen, origin mode, and character sets from s. The caller places
+ * the cursor. */
+static inline void term_cursor_apply(struct term *t, const struct term_saved_cursor *s)
+{
+ t->cur_fg = s->fg;
+ t->cur_bg = s->bg;
+ t->cur_ul = s->ul;
+ t->underline = s->underline;
+ t->bold = s->bold;
+ t->faint = s->faint;
+ t->italic = s->italic;
+ t->strike = s->strike;
+ t->overline = s->overline;
+ t->inverse = s->inverse;
+ t->origin_mode = s->origin;
+ t->charsets[0] = s->charsets[0];
+ t->charsets[1] = s->charsets[1];
+ t->charset_active = s->charset_active;
+ t->graphics = t->charsets[t->charset_active] == '0';
+}
+
+static inline void send_response(struct term *t, const char *str)
+{
+ if (t && t->cb.reply)
+ t->cb.reply(t->cb.ud, str, strlen(str));
+}
+static inline int term_hex_nib(char c)
+{
+ if (c >= '0' && c <= '9') return c - '0';
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
+ return -1;
+}
+
+static inline bool term_lnm(const struct term *t)
+{
+ return t->lnm || t->transport_newline;
+}
+
+static inline bool mode_is_mouse(int m)
+{
+ return m == 9 || (m >= 1000 && m <= 1003);
+}
+
+static inline bool mode_is_mouse_enc(int m)
+{
+ return m == 1005 || m == 1006 || m == 1015 || m == 1016;
+}
+
+static const uint16_t term_ul_flags[] = {
+ 0, CELL_UNDERLINE, CELL_DOUBLE_UNDERLINE,
+ CELL_CURLY_UNDERLINE, CELL_DOTTED_UNDERLINE,
+ CELL_DASHED_UNDERLINE,
+};
+
+static inline uint16_t term_current_flags(const struct term *t)
+{
+ uint16_t f = 0;
+ if (t->bold) f |= CELL_BOLD;
+ if (t->faint) f |= CELL_FAINT;
+ if (t->italic) f |= CELL_ITALIC;
+ if (t->underline < sizeof(term_ul_flags) / sizeof(term_ul_flags[0]))
+ f |= term_ul_flags[t->underline];
+ if (t->strike) f |= CELL_STRIKE;
+ if (t->overline) f |= CELL_OVERLINE;
+ if (t->cur_link) f |= CELL_LINK;
+ return f;
+}
+
+static inline uint32_t term_flags_pad(const struct term *t)
+{
+ return term_current_flags(t) | ((uint32_t)t->cur_link << 16);
+}
+
+static inline void term_tabs_clear(struct term *t)
+{
+ memset(t->tabs, 0, (size_t)t->tabs_cap);
+}
+
+static inline void term_tabs_default(struct term *t)
+{
+ for (int c = 8; c < t->tabs_cap; c += 8) t->tabs[c] = 1;
+}
+
+static inline bool term_str_was_truncated(struct term *t)
+{
+ bool truncated = t->str_truncated;
+ t->str_truncated = false;
+ return truncated;
+}
+
+/* Copy text into buf with the rules of term_selection_text(). NULL text
+ * copies as empty. Return the full length. */
+static inline size_t term_copy_out(const char *text, char *buf, size_t cap)
+{
+ if (!text)
+ text = "";
+ size_t n = strlen(text);
+ if (buf && cap) {
+ size_t k = n < cap ? n : cap - 1;
+ if (k) memcpy(buf, text, k);
+ buf[k] = '\0';
+ }
+ return n;
+}
+
+/* Every thread has its own anchor, so its address names the thread. */
+extern _Thread_local char term_thread_anchor;
+
+static inline uint64_t term_thread_id(void)
+{
+ return (uint64_t)(uintptr_t)&term_thread_anchor;
+}
+
+static inline void term_write_begin(struct term *t)
+{
+ if (!t) return;
+ uint64_t self = term_thread_id();
+ if (atomic_load_explicit(&t->writer_tid, memory_order_relaxed) == self) {
+ t->write_depth++;
+ return;
+ }
+ uint64_t s;
+ for (;;) {
+ s = atomic_load_explicit(&t->seq, memory_order_relaxed);
+ if (s & 1) {
+ term_cpu_relax();
+ continue;
+ }
+ if (atomic_compare_exchange_weak_explicit(&t->seq, &s, s + 1,
+ memory_order_acquire,
+ memory_order_relaxed))
+ break;
+ }
+ atomic_store_explicit(&t->writer_tid, self, memory_order_relaxed);
+ t->write_depth = 1;
+}
+
+static inline void term_write_end(struct term *t)
+{
+ if (!t) return;
+ if (--t->write_depth == 0) {
+ atomic_store_explicit(&t->writer_tid, 0, memory_order_relaxed);
+ atomic_fetch_add_explicit(&t->seq, 1, memory_order_release);
+ }
+}
+
+#endif