summaryrefslogtreecommitdiff
path: root/include/term/term.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 /include/term/term.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 'include/term/term.h')
-rw-r--r--include/term/term.h421
1 files changed, 421 insertions, 0 deletions
diff --git a/include/term/term.h b/include/term/term.h
new file mode 100644
index 0000000..4853ebf
--- /dev/null
+++ b/include/term/term.h
@@ -0,0 +1,421 @@
+#ifndef TERM_H
+#define TERM_H
+
+/*
+ * Terminal emulator core.
+ *
+ * A struct term parses the bytes that a program writes and keeps the screen,
+ * the history, and the modes. The application feeds bytes with term_feed()
+ * and reads state with the getters below. A renderer never reads the
+ * terminal directly: it copies the viewport into a struct term_snapshot and
+ * paints from that copy.
+ *
+ * All functions take the terminal as the first argument. Functions that take
+ * a const struct term do not change what it shows. A function that returns
+ * text copies it into a buffer that the caller owns.
+ *
+ * Threads. Any thread can call any function. Each call that reads or changes
+ * a terminal enters its writer section, so calls on one terminal run one at a
+ * time. A snapshot belongs to one thread at a time. term_free() is the last
+ * call on a terminal.
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "term_input.h"
+#include "term_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct term;
+struct term_snapshot;
+
+/* A row holds at most this many cells. */
+#define TERM_MAX_COLS 65535
+/* The history limit when term_options.scrollback is 0. */
+#define TERM_SCROLLBACK_DEFAULT 10000
+
+/*
+ * Colors. A term_color is either a direct color (alpha 0xFF, then RGB) or a
+ * palette slot: an index 0-255, the default foreground, or the default
+ * background. A slot resolves when a snapshot is painted, so a theme change
+ * recolors text that is already on screen.
+ */
+typedef uint32_t term_color;
+
+#define TERM_COLOR_SLOT 0x01000000u
+/* The palette index of a slot; see struct term_view.palette. */
+#define TERM_COLOR_SLOT_INDEX 0x1FFu
+#define TERM_COLOR_FG (TERM_COLOR_SLOT | 0x100u)
+#define TERM_COLOR_BG (TERM_COLOR_SLOT | 0x101u)
+
+static inline term_color term_color_index(int n)
+{
+ return TERM_COLOR_SLOT | ((uint32_t)n & 0xFFu);
+}
+
+static inline term_color term_color_rgb(uint8_t r, uint8_t g, uint8_t b)
+{
+ return 0xFF000000u | (uint32_t)r << 16 | (uint32_t)g << 8 | b;
+}
+
+static inline bool term_color_is_slot(term_color c)
+{
+ return (c >> 24) == (TERM_COLOR_SLOT >> 24);
+}
+
+/* Parse an X11 or OSC color specification ("#rgb", "rgb:r/g/b", ...). */
+TERM_API bool term_color_parse(const char *spec, uint32_t *argb);
+
+/* The colors a terminal starts with. All values are ARGB. An all-zero theme
+ * selects the built-in theme. */
+struct term_theme {
+ uint32_t fg, bg, cursor;
+ uint32_t ansi[16];
+};
+
+/* Fill theme with the built-in theme. */
+TERM_API void term_theme_default(struct term_theme *theme);
+/* Fill theme with the high-contrast theme. */
+TERM_API void term_theme_high_contrast(struct term_theme *theme);
+/* Sets one palette key: foreground, background, or color0 to color15.
+ * An empty value resets the key to the built-in theme. False, with no
+ * change, for an unknown key or a bad color. */
+TERM_API bool term_theme_set_key(struct term_theme *p, const char *key, const char *val);
+
+enum term_cursor_style {
+ TERM_CURSOR_BLOCK,
+ TERM_CURSOR_UNDERLINE,
+ TERM_CURSOR_BAR,
+};
+
+/* Mouse reporting modes and encodings. The values are the DEC mode numbers. */
+enum term_mouse {
+ TERM_MOUSE_OFF = 0,
+ TERM_MOUSE_X10 = 9,
+ TERM_MOUSE_NORMAL = 1000,
+ TERM_MOUSE_BUTTON = 1002,
+ TERM_MOUSE_ANY = 1003,
+};
+
+enum term_mouse_encoding {
+ TERM_MOUSE_ENC_X10 = 0,
+ TERM_MOUSE_ENC_UTF8 = 1005,
+ TERM_MOUSE_ENC_SGR = 1006,
+ TERM_MOUSE_ENC_URXVT = 1015,
+ TERM_MOUSE_ENC_SGR_PIXELS = 1016,
+};
+
+/*
+ * Callbacks. They run inside term_feed() and term_focus(), on the thread
+ * that calls those functions. A callback must not call back into the
+ * terminal. String arguments are valid only during the call.
+ */
+struct term_callbacks {
+ void *ud;
+ /* Bytes the terminal sends back to the program (replies to queries). */
+ void (*reply)(void *ud, const char *bytes, size_t len);
+ void (*title)(void *ud, const char *title);
+ /* OSC 52. selection names the target, such as "c". */
+ void (*clipboard)(void *ud, const char *selection, const char *data, size_t len);
+ void (*notify)(void *ud, const char *title, const char *body);
+ void (*bell)(void *ud);
+ /* Parser trace lines for debugging. NULL turns tracing off. The line has no newline. */
+ void (*trace)(void *ud, const char *line);
+};
+
+/* Options for term_new(). An all-zero value is valid. */
+struct term_options {
+ struct term_vector2i size; /* columns and rows; each is at least 1 */
+ int scrollback; /* history lines; 0 selects TERM_SCROLLBACK_DEFAULT */
+ struct term_theme theme;
+ enum term_cursor_style cursor_style;
+ bool cursor_steady; /* false: the cursor blinks */
+ struct term_callbacks callbacks;
+};
+
+/* NULL when memory runs out. */
+/* The Unicode version of the width tables, such as "18.0.0". A host can
+ * pass it to programs as UNICODE_VERSION. */
+TERM_API const char *term_unicode_version(void);
+
+/* The length of the prefix of buf made of bytes in [0x20, hi). With hi
+ * 0x7f, it finds a run of printable ASCII in UTF-8 text, so a host can
+ * skip shaping for it. Any thread can call it. */
+TERM_API size_t term_scan_printable_span(const char *buf, size_t len, unsigned char hi);
+
+/* The instruction set level of the running code: "base", "avx2", "avx512",
+ * or "neon". On x86-64, the first term_new() picks the highest level the CPU
+ * supports. Set TERM_HEADLESS_ISA to a lower level to force it, for tests
+ * and comparisons. */
+TERM_API const char *term_isa(void);
+
+TERM_API struct term *term_new(const struct term_options *opt);
+TERM_API void term_free(struct term *t);
+
+/* Hold the writer section across several calls, so that no other thread
+ * changes the terminal between them. The section is a spin lock: keep it
+ * short. The thread that holds it can call every function, and it can enter
+ * the section again. */
+TERM_API void term_lock(struct term *t);
+TERM_API void term_unlock(struct term *t);
+
+/* Change the size in cells. Returns false when memory runs out; the terminal
+ * is then unchanged. The screen reflows to the new width. */
+TERM_API bool term_resize(struct term *t, struct term_vector2i size);
+
+/* Replace the theme. A different theme also drops the colors that the
+ * program set with OSC; the same theme changes nothing. */
+TERM_API void term_set_theme(struct term *t, const struct term_theme *theme);
+
+/* The cursor style that DECSCUSR 0 and a reset restore. */
+TERM_API void term_set_cursor_default(struct term *t, enum term_cursor_style style, bool steady);
+
+/*
+ * Bytes from the program.
+ *
+ * TERM_FEED_ONLCR: a line feed also returns the cursor to column 0, as a tty
+ * with OPOST and ONLCR does. Set it when the bytes did not pass through the
+ * tty output processing (for example the fast pipe).
+ */
+enum term_feed_flags {
+ TERM_FEED_ONLCR = 1u << 0,
+};
+
+TERM_API void term_feed(struct term *t, const char *bytes, size_t len, unsigned flags);
+
+/* Report a focus change. The terminal replies only in focus-event mode. */
+TERM_API void term_focus(struct term *t, bool focused);
+
+/* Modes that affect how the application encodes input. */
+struct term_modes {
+ unsigned keyboard; /* kitty keyboard protocol flags */
+ enum term_mouse mouse;
+ enum term_mouse_encoding mouse_encoding;
+ struct term_vector2i grid; /* columns and rows */
+ bool app_cursor; /* DECCKM */
+ bool app_keypad; /* DECKPAM */
+ bool backarrow_bs; /* DECBKM */
+ bool bracketed_paste;
+ bool focus_events;
+ bool alternate; /* the alternate screen is active */
+ bool reverse_video; /* DECSCNM */
+ bool synchronized; /* mode 2026 */
+};
+
+struct term_cursor {
+ struct term_vector2i pos;
+ enum term_cursor_style style;
+ bool visible;
+ bool blink;
+};
+
+TERM_API struct term_vector2i term_size(const struct term *t);
+TERM_API struct term_modes term_modes(const struct term *t);
+
+/*
+ * Encode an input event as the bytes the program expects. Get modes from
+ * term_modes(). Record ev in keyboard with term_keyboard_update() first. Send
+ * the bytes to the program yourself.
+ */
+TERM_API struct term_input_result term_input_encode_key(
+ const struct term_keyboard_state *keyboard,
+ const struct term_key_event *ev,
+ const struct term_modes *modes,
+ char *buf, size_t capacity
+);
+
+TERM_API struct term_input_result term_input_encode_mouse(
+ const struct term_mouse_event *ev,
+ const struct term_modes *modes,
+ char *buf, size_t capacity
+);
+
+/* Bytes term_input_encode_paste() adds to the text at most. */
+#define TERM_PASTE_EXTRA 12
+
+/*
+ * Encode pasted text. Drop control characters and bidi controls, so the
+ * program sees only what the user saw. Send a line break as CR. Add the
+ * bracketed-paste markers when the program asked for them. A buffer of
+ * len + TERM_PASTE_EXTRA bytes is always large enough. Text that is empty
+ * after the filter gives TERM_INPUT_CONSUMED.
+ */
+TERM_API struct term_input_result term_input_encode_paste(
+ const struct term_modes *modes,
+ const char *text, size_t len,
+ char *buf, size_t capacity
+);
+/* The cursor in screen coordinates (not the viewport). */
+TERM_API struct term_cursor term_cursor(const struct term *t);
+/* Copy the directory from OSC 7 into buf, with the rules of
+ * term_selection_text(). Return 0 when there is none. */
+TERM_API size_t term_cwd(const struct term *t, char *buf, size_t cap);
+/* Copy the pointer shape name from OSC 22 the same way. */
+TERM_API size_t term_pointer_shape(const struct term *t, char *buf, size_t cap);
+
+/*
+ * Viewport. The viewport shows as many rows as the screen. Offset 0 shows
+ * the live screen; a larger offset shows that many rows of history above it.
+ * The alternate screen has no history, so its offset is always 0.
+ */
+struct term_scrollback {
+ int offset; /* rows between the viewport and the live screen */
+ int total; /* history rows at the current width */
+};
+
+TERM_API struct term_scrollback term_scrollback(const struct term *t);
+/* Scroll the viewport. A positive count moves back into history. */
+TERM_API void term_scroll(struct term *t, int lines);
+TERM_API void term_scroll_to(struct term *t, int offset);
+
+/*
+ * Selection, in viewport coordinates. A selection starts at one cell and
+ * extends to another; the unit widens both ends to words or whole lines.
+ * The terminal clears the selection when the text under it changes.
+ */
+enum term_select_unit {
+ TERM_SELECT_CELL,
+ TERM_SELECT_WORD,
+ TERM_SELECT_LINE,
+};
+
+/* Start a selection at cell. For TERM_SELECT_CELL, right_half means the
+ * pointer is on the right half of the cell, so the selection starts after it. */
+TERM_API void term_select_start(struct term *t, struct term_vector2i cell, bool right_half,
+ enum term_select_unit unit);
+/* Extend the selection from its start to cell. */
+TERM_API void term_select_extend(struct term *t, struct term_vector2i cell, bool right_half);
+TERM_API void term_select_clear(struct term *t);
+TERM_API bool term_has_selection(const struct term *t);
+/* Copy the selected text as UTF-8 into buf. Return its length without the NUL.
+ * With a NULL buf or a zero cap, only the length is returned. Otherwise the
+ * copy is cut to cap - 1 bytes and NUL-terminated. Return 0 when nothing is
+ * selected. Hold term_lock across the length query and the copy to keep the
+ * length stable. */
+TERM_API size_t term_selection_text(const struct term *t, char *buf, size_t cap);
+/* The OSC 8 link or the detected URL at a viewport cell. Copies it with the
+ * same rules as term_selection_text(). Return 0 when there is no link. */
+TERM_API size_t term_link_at(const struct term *t, struct term_vector2i cell, char *buf, size_t cap);
+
+/* Cell attributes. */
+enum term_attr {
+ TERM_ATTR_BOLD = 1u << 0,
+ TERM_ATTR_ITALIC = 1u << 1,
+ TERM_ATTR_FAINT = 1u << 2,
+ TERM_ATTR_STRIKE = 1u << 3,
+ TERM_ATTR_OVERLINE = 1u << 4,
+ TERM_ATTR_LINK = 1u << 5,
+ TERM_ATTR_EMOJI = 1u << 6, /* the text requested emoji presentation */
+ TERM_ATTR_TEXT = 1u << 7, /* the text requested text presentation */
+};
+
+#define TERM_ATTR_UNDERLINE_SHIFT 8
+#define TERM_ATTR_UNDERLINE_MASK (7u << TERM_ATTR_UNDERLINE_SHIFT)
+
+enum term_underline {
+ TERM_UNDERLINE_NONE,
+ TERM_UNDERLINE_SINGLE,
+ TERM_UNDERLINE_DOUBLE,
+ TERM_UNDERLINE_CURLY,
+ TERM_UNDERLINE_DOTTED,
+ TERM_UNDERLINE_DASHED,
+};
+
+static inline enum term_underline term_attr_underline(uint16_t attrs)
+{
+ return (enum term_underline)((attrs & TERM_ATTR_UNDERLINE_MASK) >> TERM_ATTR_UNDERLINE_SHIFT);
+}
+
+/* text refers to a grapheme cluster in the snapshot; see term_snapshot_cluster(). */
+#define TERM_TEXT_CLUSTER 0x80000000u
+
+/* One cell as a snapshot shows it. */
+struct term_cell {
+ uint32_t text; /* a code point, TERM_TEXT_CLUSTER | n, or 0 for an empty cell */
+ term_color fg, bg;
+ uint16_t attrs; /* enum term_attr and the underline style */
+ uint8_t width; /* 1; 2 for a wide glyph; 0 for the right half of a wide glyph */
+ uint8_t reserved;
+};
+
+#ifdef __cplusplus
+static_assert(sizeof(struct term_cell) == 16, "four cells fill one 64-byte line");
+#else
+_Static_assert(sizeof(struct term_cell) == 16, "four cells fill one 64-byte line");
+#endif
+
+/*
+ * Snapshot: a copy of the viewport for a renderer. It holds no pointer into
+ * the terminal, so it stays valid while the terminal changes. One terminal
+ * may have several snapshots, for example a window and a tab preview.
+ */
+TERM_API struct term_snapshot *term_snapshot_new(void);
+TERM_API void term_snapshot_free(struct term_snapshot *s);
+
+/* Copy the viewport of t into s. Keep rows with unchanged content keys.
+ * Update the history view index and refine counts for visible wide lines.
+ * Return false when memory runs out. The snapshot is then empty. */
+TERM_API bool term_snapshot_update(struct term_snapshot *s, struct term *t);
+
+/* Palette entries of struct term_view.palette after the 256 indexed colors. */
+enum {
+ TERM_PALETTE_FG = 256,
+ TERM_PALETTE_BG = 257,
+ TERM_PALETTE_CURSOR = 258,
+ TERM_PALETTE_SIZE = 259,
+};
+
+struct term_view {
+ struct term_vector2i size;
+ struct term_cursor cursor; /* viewport coordinates; not visible when scrolled away */
+ int scroll_offset;
+ /* Rows the content moved up since the previous update, when that movement
+ * explains the change; otherwise 0. */
+ int scrolled;
+ bool reverse_video;
+ /* Changes when any part of the view changes. */
+ uint64_t version;
+ /* Changes when a palette entry changes. */
+ uint64_t palette_version;
+ const uint32_t *palette; /* TERM_PALETTE_SIZE ARGB entries */
+};
+
+TERM_API const struct term_view *term_snapshot_view(const struct term_snapshot *s);
+/* The cells of viewport row y: view->size.x entries. */
+TERM_API const struct term_cell *term_snapshot_row(const struct term_snapshot *s, int y);
+/* An identity for the content of row y. Two rows with the same key hold the
+ * same cells, also when the content moved to another row. */
+TERM_API uint64_t term_snapshot_row_key(const struct term_snapshot *s, int y);
+
+/* Number of rows in history above the screen. 0 if alternate screen or no history. */
+TERM_API int term_history_rows(const struct term *t);
+/* Copy up to max_rows history rows before the current viewport as UTF-8 into
+ * buf, with the same rules as term_selection_text(). Join soft-wrapped rows.
+ * End closed lines with a newline. This does not change the selection.
+ * Return 0 when there is no history text, and also when the text cannot be
+ * built. */
+TERM_API size_t term_history_text(struct term *t, int max_rows, char *buf, size_t cap);
+TERM_API struct term_span term_snapshot_row_selection(const struct term_snapshot *s, int y);
+
+/* The code points of a cluster cell. Returns the number of code points and
+ * writes at most cap of them. */
+TERM_API int term_snapshot_cluster(const struct term_snapshot *s, uint32_t text, uint32_t *cps, int cap);
+
+/* The ARGB value of a color in a view. */
+static inline uint32_t term_view_color(const struct term_view *v, term_color c)
+{
+ if (!term_color_is_slot(c))
+ return c;
+ return v->palette[c & TERM_COLOR_SLOT_INDEX];
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif