diff options
Diffstat (limited to 'src/screen.h')
| -rw-r--r-- | src/screen.h | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/src/screen.h b/src/screen.h new file mode 100644 index 0000000..0110049 --- /dev/null +++ b/src/screen.h @@ -0,0 +1,453 @@ +#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_ring_row(const struct term *t, int row); +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 struct screen *term_active_screen(const struct term *t) +{ + return (struct screen *)&t->scr; +} + +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; +} + +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) +{ + int p = t->scr.top + y; + struct srow *row = &t->scr.slot[p >= t->scr.rows ? p - t->scr.rows : p]; + srow_materialize_text(row); + return row; +} + +static inline struct srow *term_screen_row(const struct term *t, int y) +{ + return term_row(t, 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 = text_row_last_pen(&prev->text); + } + if (row->kind == ROW_TEXT) { + uint32_t sl = pen_sl(&start); + pen_set(&row->text.start, start.fg, start.bg, sl); + pen_set(&row->text.runs.base, start.fg, start.bg, sl); + pen_set(&row->text.runs.pen, start.fg, start.bg, sl); + 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 fg, bg, sl (flags | link << 16). + * Runs count characters. */ +static inline void text_commit(struct srow *row, int nbytes, int chars, int cells, uint8_t hflags, + uint32_t fg, uint32_t bg, uint32_t sl) +{ + 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) | + ((sl >> 16) ? HLINE_HAS_LINK : 0)); + hist_runs_add_pen(&tr->runs, fg, bg, sl, (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, uint32_t fg, uint32_t bg, uint32_t sl) +{ + bytes_copy_small(row->text.text + row->text.text_len, (const char *)bytes, nbytes); + text_commit(row, nbytes, chars, cells, hflags, fg, bg, sl); +} + +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, uint32_t fg, uint32_t bg) +{ + 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++; + (void)fg; + (void)bg; +} + +static inline void row_fill_gap(struct term *t, int p, struct cell *row, int x) +{ + struct screen *s = term_active_screen(t); + struct srow *r = &s->slot[p]; + if (x > r->len) { + if (!(r->flags & RF_TAIL_PADDED)) + fill_cells(row + r->len, x - r->len, TERM_COLOR_FG, TERM_COLOR_BG); + r->len = (uint16_t)x; + } +} + +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 screen *s = term_active_screen(t); + struct srow *row = &s->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, t->cur_fg, t->cur_bg); + 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) { + row_fill_gap(t, p, cells, 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, t->cur_fg, t->cur_bg); + } else { + row->len = (uint16_t)from; + row->flags &= ~RF_TAIL_PADDED; + row->version++; + } +} + +static inline int erase_width(const struct term *t, int p) +{ + const struct screen *s = term_active_screen(t); + return s->slot[p].len > t->cols ? s->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 |
