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
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
|
#ifndef TERM_SCREEN_H
#define TERM_SCREEN_H
#include <assert.h>
#include "compat.h"
#include "internal.h"
#include "kernels.h"
#include "simd.h"
void term_screen_scroll_region(struct term *t, int top, int bottom, int count, bool down, bool history);
void term_screen_set_alternate(struct term *t, bool enabled);
void term_screen_erase(struct term *t, int mode);
void term_screen_erase_all(struct term *t);
void term_screen_erase_line(struct term *t, int mode);
void term_screen_delete_chars(struct term *t, int count);
void term_screen_insert_chars(struct term *t, int count);
const struct cell *term_screen_view_row(const struct term *t, int y);
uint64_t term_screen_view_row_key(const struct term *t, int y);
/* The selected columns [c0, c1] of each view row; c1 < c0 when none. */
void term_selection_view_spans(const struct term *t, int *c0, int *c1);
int term_screen_history_rows(const struct term *t);
/* Rows of history above the live screen. The view owns the offset. */
int term_view_offset(const struct term *t);
static inline bool screen_has_history(const struct term *t)
{
return !t->alternate && t->hist.line_limit > 0;
}
static inline int phys_row(const struct term *t, int y)
{
int p = t->scr.top + y;
return p >= t->scr.rows ? p - t->scr.rows : p;
}
#if defined(TERM_KERNEL_AVX2)
TERM_TARGET_AVX2
static inline void set_cells_wrap_pad_avx2(struct cell *cells, int count)
{
const __m256i mask = _mm256_setr_epi32(0, 0, 0, (int)CELL_WRAP_PAD,
0, 0, 0, (int)CELL_WRAP_PAD);
int i = 0;
for (; i + 4 <= count; i += 4) {
__m256i v0 = _mm256_loadu_si256((const __m256i *)(cells + i));
__m256i v1 = _mm256_loadu_si256((const __m256i *)(cells + i + 2));
v0 = _mm256_or_si256(v0, mask);
v1 = _mm256_or_si256(v1, mask);
_mm256_storeu_si256((__m256i *)(cells + i), v0);
_mm256_storeu_si256((__m256i *)(cells + i + 2), v1);
}
for (; i < count; i++)
cells[i].flags |= CELL_WRAP_PAD;
}
#endif
static inline void set_cells_wrap_pad(struct cell *cells, int count)
{
if (count <= 0)
return;
#if defined(TERM_KERNEL_AVX2)
if (term_use_avx2() && count >= 4) {
set_cells_wrap_pad_avx2(cells, count);
return;
}
#endif
#if defined(__x86_64__) || defined(_M_X64)
const __m128i mask = _mm_setr_epi32(0, 0, 0, (int)CELL_WRAP_PAD);
int i = 0;
for (; i + 2 <= count; i += 2) {
__m128i v0 = _mm_loadu_si128((const __m128i *)(cells + i));
__m128i v1 = _mm_loadu_si128((const __m128i *)(cells + i + 1));
_mm_storeu_si128((__m128i *)(cells + i), _mm_or_si128(v0, mask));
_mm_storeu_si128((__m128i *)(cells + i + 1), _mm_or_si128(v1, mask));
}
for (; i < count; i++)
cells[i].flags |= CELL_WRAP_PAD;
#elif defined(__aarch64__)
const uint32x4_t mask = { 0, 0, 0, CELL_WRAP_PAD };
int i = 0;
for (; i + 2 <= count; i += 2) {
uint32x4_t v0 = vld1q_u32((const uint32_t *)(cells + i));
uint32x4_t v1 = vld1q_u32((const uint32_t *)(cells + i + 1));
vst1q_u32((uint32_t *)(cells + i), vorrq_u32(v0, mask));
vst1q_u32((uint32_t *)(cells + i + 1), vorrq_u32(v1, mask));
}
for (; i < count; i++)
cells[i].flags |= CELL_WRAP_PAD;
#else
for (int i = 0; i < count; i++)
cells[i].flags |= CELL_WRAP_PAD;
#endif
}
static inline void init_ascii_text_row(struct text_row *tr, uint8_t *text, uint16_t len)
{
tr->text = text;
tr->text_len = tr->cells = len;
tr->wide = tr->fill_to = 0;
tr->flags = HLINE_NARROW;
tr->runs.len = 0;
tr->runs.count = len;
#if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__)
static_assert(sizeof(struct pen_attr) == 12,
"a 16-byte store writes one pen and 4 spare bytes");
static_assert(offsetof(struct hist_runs, base) + 12 == offsetof(struct hist_runs, pen),
"the spare bytes of the store at base land on pen");
static_assert(offsetof(struct hist_runs, pen) + 16 <= sizeof(struct hist_runs),
"the store at pen stays inside hist_runs");
static_assert(offsetof(struct text_row, start) + 16 <= offsetof(struct text_row, runs),
"the store at start stays before runs");
#endif
/* The extra lanes cover unused fill data and run padding. */
#if defined(__x86_64__) || defined(_M_X64)
__m128i pen = _mm_setr_epi32(TERM_COLOR_FG, TERM_COLOR_BG, 0, TERM_COLOR_FG);
_mm_storeu_si128((__m128i *)&tr->start, pen);
_mm_storeu_si128((__m128i *)&tr->runs.base, pen);
_mm_storeu_si128((__m128i *)&tr->runs.pen, pen);
#elif defined(__aarch64__)
uint32x4_t pen = { TERM_COLOR_FG, TERM_COLOR_BG, 0, TERM_COLOR_FG };
vst1q_u32((uint32_t *)&tr->start, pen);
vst1q_u32((uint32_t *)&tr->runs.base, pen);
vst1q_u32((uint32_t *)&tr->runs.pen, pen);
#else
tr->start = tr->runs.base = tr->runs.pen = pen_attr_default();
#endif
}
static inline void srow_materialize_text(struct srow *row)
{
if (row->kind == ROW_ASCII) {
init_ascii_text_row(&row->text, row->text.text, row->len);
row->kind = ROW_TEXT;
}
}
static inline struct srow *screen_get_row(const struct screen *s, int y)
{
int p = s->top + y;
struct srow *row = &s->slot[p >= s->rows ? p - s->rows : p];
srow_materialize_text(row);
return row;
}
/* The row at y, or NULL when y is outside the screen. A view row that
* resolves to no screen row must not index the ring. */
static inline struct srow *screen_row_at(const struct screen *s, int y)
{
if (y < 0 || y >= s->rows)
return NULL;
return screen_get_row(s, y);
}
static inline struct srow *term_row(const struct term *t, int y)
{
return screen_get_row(&t->scr, y);
}
/* Write the cells of a text row to out: its text, then its fill blanks.
* Return the cells written. */
static inline int text_row_decode(const struct srow *row, struct cell *out, int cols)
{
srow_materialize_text((struct srow *)row);
const struct text_row *tr = &row->text;
int n = term_hist_text_decode(tr, out, cols, NULL);
int fill = tr->fill_to < cols ? tr->fill_to : cols;
if (n < fill) {
fill_cells(out + n, fill - n, tr->fill.fg, tr->fill.bg);
n = fill;
}
return n;
}
/* The cells of a text row, cols wide, for a reader. */
static inline void text_row_cells(const struct srow *row, struct cell *out, int cols)
{
int n = text_row_decode(row, out, cols);
if (n < cols)
fill_cells(out + n, cols - n, TERM_COLOR_FG, TERM_COLOR_BG);
}
/* Turn a text row into a cells row. Only the write core does this. Its
* cells past the text are still blank when RF_TAIL_PADDED says so. */
static inline void srow_settle(struct srow *row, int cols)
{
srow_materialize_text(row);
if (row->kind == ROW_TEXT) {
int n = text_row_decode(row, row->cells, cols);
if (n < cols && !(row->flags & RF_TAIL_PADDED))
fill_cells(row->cells + n, cols - n, TERM_COLOR_FG, TERM_COLOR_BG);
row->flags |= RF_TAIL_PADDED;
row->kind = ROW_CELLS;
}
}
/*
* The cursor row as a row that text can append to: a text row with the
* cursor at its end, or an empty row on the bottom line of a full-screen
* region with history, which becomes a text row. Rows there scroll into
* history, so they keep the history format. NULL when neither holds. The
* caller has handled a pending wrap, insert mode, and margins.
*/
static inline struct srow *text_append_row(struct term *t)
{
struct srow *row = term_row(t, t->cursor_y);
if (row->kind == ROW_TEXT) {
text_row_unshare(&row->text);
if (t->cursor_x != row->text.cells)
return NULL;
if (row->text.cells)
return row;
} else if (row->len != 0 || t->cursor_x != 0 || t->cursor_y != t->rows - 1 ||
t->scroll_top != 0 || t->scroll_bottom != t->rows - 1 || !screen_has_history(t)) {
return NULL;
}
/* A row that continues a text row starts from that row's last pen, so
* history can copy its runs. */
struct pen_attr start = pen_attr_default();
if (t->cursor_y > 0) {
const struct srow *prev = term_row(t, t->cursor_y - 1);
if (prev->kind == ROW_TEXT && (prev->flags & RF_WRAPPED))
start = prev->text.runs.pen;
}
if (row->kind == ROW_TEXT) {
struct text_pen p = text_pen_from_attr(&start);
pen_set_text(&row->text.start, p);
pen_set_text(&row->text.runs.base, p);
pen_set_text(&row->text.runs.pen, p);
return row;
}
row->kind = ROW_TEXT;
text_row_start(&row->text, &start);
return row;
}
/* Take nbytes bytes that a writer put at the end of the row text: chars
* characters in cells columns, with the pen. Runs count characters. */
static inline void text_commit(struct srow *row, int nbytes, int chars, int cells, uint8_t hflags,
struct text_pen pen)
{
struct text_row *tr = &row->text;
tr->text_len = (uint16_t)(tr->text_len + nbytes);
tr->cells = (uint16_t)(tr->cells + cells);
tr->wide = (uint16_t)(tr->wide + cells - chars);
tr->flags = (uint8_t)((tr->flags & hflags & HLINE_NARROW) |
((tr->flags | hflags) & ~HLINE_NARROW) |
((pen.sl >> 16) ? HLINE_HAS_LINK : 0));
hist_runs_add_pen(&tr->runs, pen, (uint32_t)chars);
if (tr->cells > row->len)
row->len = tr->cells;
row->version++;
if (hflags & HLINE_HAS_WIDE)
row->flags |= RF_WIDE;
}
static inline void text_append(struct srow *row, const uint8_t *bytes, int nbytes, int chars,
int cells, uint8_t hflags, struct text_pen pen)
{
bytes_copy_small(row->text.text + row->text.text_len, (const char *)bytes, nbytes);
text_commit(row, nbytes, chars, cells, hflags, pen);
}
static inline struct cell *row_cells(const struct term *t, int p)
{
struct srow *row = &t->scr.slot[p];
if (row->kind != ROW_CELLS)
srow_settle(row, t->cols);
return row->cells;
}
/* Screen rows carry no line ids. A row's logical line follows from the
* history tail and the RF_WRAPPED chain above it, so a scroll, a wrap change,
* or a resize can never leave a stale id behind. */
/* The logical line of screen row 0, and the row's segment in it. */
static inline struct line_pos screen_top_line(const struct term *t)
{
if (!screen_has_history(t))
return (struct line_pos){ t->scr.top_id, t->scr.top_seg };
uint64_t end = hist_end_id(&t->hist);
if (!hist_is_open(&t->hist))
return (struct line_pos){ end, 0 };
return (struct line_pos){ end - 1, (uint32_t)term_hist_line_rows(&t->hist, end - 1, t->cols) };
}
static inline struct line_pos line_pos_next(struct line_pos p, const struct srow *row)
{
return (row->flags & RF_WRAPPED) ? (struct line_pos){ p.id, p.seg + 1 }
: (struct line_pos){ p.id + 1, 0 };
}
/* The oldest line that history or the screen still holds. */
static inline uint64_t first_retained_line(const struct term *t)
{
if (screen_has_history(t) && t->hist.lines)
return hist_first_id(&t->hist);
return screen_top_line(t).id;
}
static inline void row_set_wrapped(struct srow *row, bool wrapped)
{
row->flags = wrapped ? (row->flags | RF_WRAPPED) : (row->flags & ~RF_WRAPPED);
}
static inline void clear_wide_edges(struct cell *row, int from, int to, int len)
{
if (from >= to) return;
if (from > 0 && from < len && (row[from].flags & CELL_TRAIL))
fill_cells(row + from - 1, 1, row[from - 1].fg, row[from - 1].bg);
if (to < len && (row[to].flags & CELL_TRAIL))
fill_cells(row + to, 1, row[to].fg, row[to].bg);
}
static inline void repair_wide_row(struct cell *row, int from, int to, int cols, int len)
{
if (from < 0) from = 0;
if (to > len) to = len;
if (to > cols) to = cols;
for (int x = from; x < to; x++) {
if ((row[x].flags & CELL_TRAIL) &&
(x == 0 || !(row[x - 1].flags & CELL_WIDE)))
fill_cells(row + x, 1, row[x].fg, row[x].bg);
if ((row[x].flags & CELL_WIDE) && cols > 1 &&
(x + 1 == cols || x + 1 >= len || !(row[x + 1].flags & CELL_TRAIL)))
fill_cells(row + x, 1, row[x].fg, row[x].bg);
}
}
static inline bool using_default_colors(const struct term *t)
{
return t->cur_fg == TERM_COLOR_FG && t->cur_bg == TERM_COLOR_BG;
}
static inline void mark_row_blank(struct term *t, int p)
{
struct srow *row = &t->scr.slot[p];
uint8_t tail_padded = ((row->kind == ROW_CELLS && row->len == 0) || row->kind == ROW_TEXT)
? (row->flags & RF_TAIL_PADDED) : 0;
row->kind = ROW_CELLS;
row->len = 0;
row->flags = tail_padded;
row->version++;
}
static inline void term_row_fill_gap(struct srow *row, int x)
{
if (x > row->len) {
if (!(row->flags & RF_TAIL_PADDED))
fill_cells(row->cells + row->len, x - row->len, TERM_COLOR_FG, TERM_COLOR_BG);
row->len = (uint16_t)x;
}
}
static inline void write_cell_span(struct cell *row, int x,
struct cell cell, int span)
{
row[x] = cell;
if (span == 2) {
row[x + 1] = cell;
row[x + 1].c = 0;
row[x + 1].flags = (uint16_t)((cell.flags & ~CELL_WIDE) | CELL_TRAIL);
}
}
static inline void erase_span(struct term *t, int p, int from, int to)
{
bool pristine = using_default_colors(t);
struct srow *row = &t->scr.slot[p];
int len = row->len;
if (to > TERM_MAX_COLS) to = TERM_MAX_COLS;
if (pristine && to > len) to = len;
if (from >= to) return;
if (pristine && from == 0 && to >= len) {
mark_row_blank(t, p);
return;
}
struct cell *cells = row_cells(t, p);
if (from > 0 && from < len && (cells[from].flags & CELL_TRAIL)) from--;
if (to < len && (cells[to].flags & CELL_TRAIL)) to++;
if (to >= t->cols)
row_set_wrapped(row, false);
if (!pristine) {
term_row_fill_gap(row, from);
fill_cells(cells + from, to - from, t->cur_fg, t->cur_bg);
if (to > len) {
row->len = (uint16_t)to;
if (to < t->cols)
row->flags &= ~RF_TAIL_PADDED;
}
row->version++;
return;
}
if (to < len) {
fill_cells(cells + from, to - from, t->cur_fg, t->cur_bg);
row->version++;
return;
}
if (from == 0) {
mark_row_blank(t, p);
} else {
row->len = (uint16_t)from;
row->flags &= ~RF_TAIL_PADDED;
row->version++;
}
}
static inline int erase_width(const struct term *t, int p)
{
return t->scr.slot[p].len > t->cols ? t->scr.slot[p].len : t->cols;
}
static inline void erase_row(struct term *t, int p)
{
erase_span(t, p, 0, erase_width(t, p));
}
static inline void clamp_selection_after_scroll(struct term *t)
{
if (TERM_EXPECT(!t->selection, 1))
return;
uint64_t first_id = first_retained_line(t);
if (t->sel_end.line_id < first_id) {
t->selection = false;
} else if (t->sel_start.line_id < first_id) {
t->sel_start.line_id = first_id;
t->sel_start.cell_off = 0;
}
}
/* Move a row that leaves the top of the normal screen into history. */
void term_screen_push_row(struct term *t, struct srow *row);
/* Count a row that leaves the top of a screen without history. */
static inline void screen_drop_top(struct screen *s, const struct srow *row)
{
struct line_pos p = line_pos_next((struct line_pos){ s->top_id, s->top_seg }, row);
s->top_id = p.id;
s->top_seg = p.seg;
}
/* Clear a row that enters at the bottom. With a colored pen it is a text row
* that is all fill: blanks in the pen colors, which text can append over. */
static inline void screen_new_row(struct term *t, struct srow *row)
{
row->uid = ++t->next_row_uid;
row->version++;
row->flags = ((row->kind == ROW_CELLS && row->len == 0) || row->kind == ROW_TEXT)
? (row->flags & RF_TAIL_PADDED) : 0;
if (TERM_EXPECT(using_default_colors(t), 1)) {
row->kind = ROW_CELLS;
row->len = 0;
} else {
row->kind = ROW_TEXT;
text_row_start(&row->text, &(struct pen_attr){ .fg = TERM_COLOR_FG, .bg = TERM_COLOR_BG });
row->text.fill_to = (uint16_t)t->scr.cols;
row->text.fill = (struct pen_attr){ .fg = t->cur_fg, .bg = t->cur_bg };
row->len = (uint16_t)t->scr.cols;
}
}
static inline void term_screen_scroll_up(struct term *t)
{
struct screen *s = &t->scr;
struct srow *row = &s->slot[s->top];
if (TERM_EXPECT(screen_has_history(t), 1))
term_screen_push_row(t, row);
else
screen_drop_top(s, row);
if (TERM_EXPECT(t->prompt_y > 0, 0) && !t->alternate)
t->prompt_y--;
s->top = s->top + 1 == s->rows ? 0 : s->top + 1;
clamp_selection_after_scroll(t);
screen_new_row(t, row);
t->full_gen++;
}
#endif
|