summaryrefslogtreecommitdiff
path: root/include/term/term.h
blob: dc41114d29198ac3984346e6b7982bd9a137f238 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
#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. Selection, history, link,
 * directory, and pointer-shape queries return owned strings; release them
 * with term_text_free().
 *
 * 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 clipboard callback.
     * selection names the target clipboard buffer(s), commonly:
     *   "c" - clipboard (system clipboard)
     *   "p" - primary selection (X11 primary)
     *   "q" - secondary selection
     *   "s" - select selection
     *   "0".."7" - cut buffers 0 to 7
     * A parameter may contain multiple targets (e.g. "cp").
     * data contains decoded bytes of length len.
     */
    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);
/* Owned, NUL-terminated text from OSC 7 and OSC 22. NULL when unavailable
 * or allocation fails. The strings stay valid after the terminal changes
 * or is freed. Release them with term_text_free(). */
TERM_API char *term_cwd(const struct term *t);
TERM_API char *term_pointer_shape(const struct term *t);
/* Release any owned text returned by the library. NULL is safe. */
TERM_API void term_text_free(char *text);

/*
 * 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);
/* Owned, NUL-terminated UTF-8 of the selection. NULL when nothing is
 * selected or allocation fails; a selection of blanks may produce an empty
 * string. Capture is atomic with respect to other calls on t. Release the
 * result with term_text_free(). */
TERM_API char *term_selection_text(const struct term *t);
/* Owned OSC 8 link or detected URL at a viewport cell. NULL when there is
 * no link or allocation fails. Release the result with term_text_free(). */
TERM_API char *term_link_at(const struct term *t, struct term_vector2i cell);

/* Cell attributes share the write core's bit layout. Underline styles are
 * mutually exclusive flags, not a shifted enum; use term_attr_underline(). */
enum term_attr {
    TERM_ATTR_BOLD = 1u << 0,
    TERM_ATTR_ITALIC = 1u << 1,
    TERM_ATTR_UNDERLINE = 1u << 2,
    TERM_ATTR_STRIKE = 1u << 3,
    TERM_ATTR_OVERLINE = 1u << 4,
    TERM_ATTR_EMOJI = 1u << 5,        /* the text requested emoji presentation */
    TERM_ATTR_TEXT = 1u << 6,         /* the text requested text presentation */
    TERM_ATTR_FAINT = 1u << 10,
    TERM_ATTR_DOUBLE_UNDERLINE = 1u << 11,
    TERM_ATTR_CURLY_UNDERLINE = 1u << 12,
    TERM_ATTR_DOTTED_UNDERLINE = 1u << 13,
    TERM_ATTR_DASHED_UNDERLINE = 1u << 14,
    TERM_ATTR_LINK = 1u << 15,
};

#define TERM_ATTR_UNDERLINE_MASK \
    (TERM_ATTR_UNDERLINE | TERM_ATTR_DOUBLE_UNDERLINE | TERM_ATTR_CURLY_UNDERLINE | \
     TERM_ATTR_DOTTED_UNDERLINE | TERM_ATTR_DASHED_UNDERLINE)

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)
{
    if (attrs & TERM_ATTR_DOUBLE_UNDERLINE) return TERM_UNDERLINE_DOUBLE;
    if (attrs & TERM_ATTR_CURLY_UNDERLINE) return TERM_UNDERLINE_CURLY;
    if (attrs & TERM_ATTR_DOTTED_UNDERLINE) return TERM_UNDERLINE_DOTTED;
    if (attrs & TERM_ATTR_DASHED_UNDERLINE) return TERM_UNDERLINE_DASHED;
    return attrs & TERM_ATTR_UNDERLINE ? TERM_UNDERLINE_SINGLE : TERM_UNDERLINE_NONE;
}

/* 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 flags; at most one underline flag */
    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);
/* Owned, NUL-terminated UTF-8 of up to max_rows history rows before the
 * current viewport. Join soft-wrapped rows and end closed lines with a
 * newline. Does not change the selection. No history or max_rows <= 0 gives
 * an empty string; NULL means allocation failed or t is NULL. Release the
 * result with term_text_free(). */
TERM_API char *term_history_text(struct term *t, int max_rows);
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