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
|
# term
term is a headless terminal core: the parser, screen ring, logical-line
history store, write core, and viewport snapshots. It is a C11 library
with no dependency but libm.
| Path | Contents |
| --- | --- |
| `include/term/` | The public headers: `term.h`, and the header-only `unicode/` |
| `src/` | The core. Its headers are private. |
| `tests/` | Unit, model, fuzz, and portability tests, and the Kitty parser suite |
| `benchmarks/` | `term-write-bench`, `term-resize-bench`, and `hist-bench` |
| `tools/` | `amalgamate.py` and `generate_unicode.py` |
`meson setup build && meson test -C build` builds and tests it alone.
## Embedding
Pick one of these:
- **Meson subproject**: `dependency('term')`, or
`subproject('term').get_variable('term_dep')`. The `arch_flag` option
sets the lowest instruction set, such as `-march=x86-64-v3`.
- **Amalgamation**: run `tools/amalgamate.py . OUT`. It writes
`OUT/term.c` and `OUT/term.h`. Compile `term.c` with any C11 compiler
and link libm. A Rust `build.rs` needs one
`cc::Build::new().file("term.c").compile("term")`.
- **Sources**: compile `src/*.c` and `src/osc/*.c` once, with `src` and
`include/term` on the include path.
The library needs nothing else. The `*-amalgamation` tests run the term
tests against the generated `term.c`, and `term-cross-test` compiles it
for aarch64, macOS, and Windows.
### SIMD level
The hot paths have AVX2, AVX-512, and NEON versions. Without them,
scroll and line-feed output runs 8 to 30 times slower. The build needs
no flags to get them:
- On x86-64 with GCC or Clang, each kernel compiles at every level with
a target attribute. The first `term_new()` picks the highest level the
CPU supports. The levels are `base` (x86-64), `avx2` (x86-64-v3), and
`avx512` (x86-64-v4 with VBMI and VBMI2). `term_isa()` names the level
in use. Set `TERM_HEADLESS_ISA` to a lower level to force it.
- `-march` sets the lowest level. The checks for that level and below
become constants, and their kernels inline into their callers.
- aarch64 always has NEON, so it needs no choice at run time.
- MSVC gets only the levels that its `/arch` flag enables.
`simd.h` states the rules for a new kernel.
- `term.h` is the only header a host includes. It includes
`term_input.h` and `term_types.h`. It compiles as C11 and as C++.
- Every global symbol starts with `term_`. The library builds with hidden
symbols, so a shared build exports only the `TERM_API` functions of
`term.h`. `term-symbols-test` checks both.
- Any thread can call any function. Calls on one terminal run one at a
time. `term.h` states the rules.
- The `static inline` helpers (`term_color_*`, `term_view_color`,
`term_attr_underline`) only decode documented bit layouts. A binding
generator such as bindgen skips them. Mirror them in the binding, or
use the generator's static-function wrappers.
A host wires the terminal in four places:
1. Create it with `term_new()`. Set `term_options.callbacks.reply` to
send replies to the program.
2. Feed program output with `term_feed()`.
3. Translate key and mouse events into `struct term_key_event` and
`struct term_mouse_event`. Encode them with `term_input_encode_key()`
and `term_input_encode_mouse()`, and send the bytes to the program.
4. Paint from a `struct term_snapshot`. The terminal gives code points,
colors, and attributes. The host shapes and draws the glyphs.
## Files and responsibilities
The files are in `src/`.
| File | Responsibility |
| --- | --- |
| `term.c` | Mode queries, cursor options, mouse reports, focus events |
| `core.c`, `core.h` | Unified write core primitives (`sc_print_ascii`, `sc_linefeed`, etc.) |
| `screen.c`, `screen.h` | Screen ring buffer, line feed, editing, resize, and reflow |
| `hist.c`, `hist.h` | Logical-line history store, SIMD cell encoder, and page arena |
| `hist_view.c`, `hist_view.h` | Viewport anchor, history row lookup, and scroll offset |
| `snapshot.c` | Immutable view snapshot, row keys, and scroll movement detection |
| `selection.c` | Text selection, coordinate mapping, and UTF-8 export |
| `gstore.c`, `gstore.h` | Per-terminal grapheme cluster table |
| `text.c` | Byte stream decoder, control character dispatch, and text run routing |
| `text_run.c`, `text_run.h` | SIMD UTF-8 text run blit |
| `fastcsi.c` | Fast CSI sequence parser |
| `input.c` | Key and mouse encoding |
| `escape.c` | Parser state machine for ESC, CSI, DCS, and OSC |
| `pen.h` | SGR graphics rendition state |
| `osc/dispatch.c` | OSC command dispatcher and palette queries |
| `osc/link.c` | OSC 8 hyperlink table |
| `osc/clipboard.c` | OSC 52 clipboard reads and writes |
| `osc/misc.c` | Operating system commands: title, directory, notifications |
## Architecture
### Write core
All text and control sequences route through unified primitives in
`core.c`. Primitives handle character printing, line feeds, margins,
and cursor movement.
### Screen and storage
The visible screen is a circular ring of rows (`struct screen`).
Rows that scroll off the top move into the logical-line history store
(`struct hist`).
The history store compacts lines into UTF-8 text and delta-encoded
attribute runs.
### Viewport and snapshots
The viewport names a logical line and a wrapped segment, or the live bottom.
The anchor also keeps the cell offset before wrapping.
Output keeps the anchor on the same text.
Resize maps that cell offset to a segment at the new width.
Eviction clamps the anchor to the oldest retained line.
A line can lose its prefix when it exceeds the history byte limit.
The anchor tracks the removed cells to keep its text position.
The alternate screen has no history.
Relative scroll walks from the anchor.
Absolute scroll uses a circular Fenwick index of line row counts.
Snapshot capture updates this index.
The write path does not update the index.
History descriptors keep row totals for the current width.
A width change starts wide lines with estimated row counts.
Snapshot capture makes the counts exact for lines in the view.
Counts above 254 rows use a separate cache.
That cache grows only when a visited line needs it.
`term_snapshot_update` takes a mutable terminal.
It updates history query metadata before it copies the view.
Snapshot getters and history getters do not change terminal state.
Renderers do not read terminal memory directly. Renderers read an
immutable snapshot (`struct term_snapshot`).
Snapshots track row keys based on row content identities.
When output scrolls, snapshots detect the scroll distance `k`.
Renderers shift existing pixels and draw records by `k` rows and paint
only changed rows.
|