#ifndef TERM_SCREEN_H #define TERM_SCREEN_H #include #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