#ifndef WSAP_TERM_CORE_H #define WSAP_TERM_CORE_H #include "compat.h" #include "internal.h" #include "screen.h" #include #include #include struct span { struct srow *row; struct cell *cells; int avail; int x; int old_len; }; /* Begin writing a span of cells. Settle cursor row, repair left edge. */ static inline struct span sc_span_begin(struct term *t, int want); /* Finish writing a span of cells. Repair right edge, raise len, bump gen. */ static inline void sc_span_end(struct term *t, struct span *s, int cols, uint8_t row_flags, uint32_t last); /* Print ASCII characters through span and SIMD chunking. */ static inline void sc_print_ascii(struct term *t, const char *s, int n); /* Print one glyph (single codepoint or cluster handle). */ void term_sc_print_glyph(struct term *t, uint32_t cp, int width, uint16_t flags); /* Extend the last printed glyph into a grapheme cluster. */ bool term_sc_extend_cluster(struct term *t, uint32_t cp, int klass); /* Multi-line or region-constrained line feed helper. */ void term_sc_linefeed_slow(struct term *t, size_t n); /* Line feed: move down or scroll with BCE. */ static inline void sc_linefeed(struct term *t, size_t n) { if (TERM_EXPECT(n == 1 && t->scroll_top == 0 && t->scroll_bottom == t->rows - 1, 1)) { t->pending_wrap = false; term_row(t, t->cursor_y)->version++; if (TERM_EXPECT(t->cursor_y == t->rows - 1, 1)) term_screen_scroll_up(t); else t->cursor_y++; return; } term_sc_linefeed_slow(t, n); } /* Carriage return: move cursor to left margin. */ static inline void sc_cr(struct term *t) { t->pending_wrap = false; if (t->cursor_x != 0) { t->cursor_x = 0; term_row(t, t->cursor_y)->version++; } } /* Move cursor to (x, y), margin-aware. */ static inline void sc_move(struct term *t, int x, int y) { t->pending_wrap = false; if (x < 0) x = 0; if (x >= t->cols) x = t->cols - 1; if (y < 0) y = 0; if (y >= t->rows) y = t->rows - 1; int old_y = t->cursor_y; if (x == t->cursor_x && y == old_y) return; t->cursor_x = x; t->cursor_y = y; term_row(t, old_y)->version++; if (y != old_y) term_row(t, y)->version++; } /* Advance cursor by n tab stops. */ void term_sc_tab(struct term *t, int n); /* Move cursor back one column. */ static inline void sc_bs(struct term *t) { t->pending_wrap = false; if (t->cursor_x > 0) t->cursor_x--; term_row(t, t->cursor_y)->version++; } /* Erase cells in span [from, to) on physical row y. */ static inline void sc_erase_span(struct term *t, int y, int from, int to) { t->pending_wrap = false; if (to > t->cols) to = t->cols; int p = phys_row(t, y); erase_span(t, p, from, to); t->scr.slot[p].version++; } /* Erase display mode 0, 1, 2, or 3. */ static inline void sc_erase_display(struct term *t, int mode) { t->pending_wrap = false; term_screen_erase(t, mode); } /* Erase line mode 0, 1, or 2. */ static inline void sc_erase_line(struct term *t, int mode) { t->pending_wrap = false; term_screen_erase_line(t, mode); } /* Insert n blank characters at cursor. */ static inline void sc_insert_chars(struct term *t, int n) { t->pending_wrap = false; term_screen_insert_chars(t, n); } /* Delete n characters at cursor. */ static inline void sc_delete_chars(struct term *t, int n) { t->pending_wrap = false; term_screen_delete_chars(t, n); } /* Scroll margins: top to bottom, n lines up or down. */ void term_sc_scroll_region(struct term *t, int top, int bottom, int n, bool down); /* Scroll the active scroll region up or down by n lines (with history). */ void term_sc_scroll(struct term *t, int n, bool down); /* Restore saved cursor state and position. */ static inline void sc_restore_cursor(struct term *t, const struct term_saved_cursor *s) { term_cursor_apply(t, s); sc_move(t, s->x, s->y); t->pending_wrap = s->pending_wrap; } /* Change attributes in a rectangular or stream area (DECCARA). */ void term_sc_deccara(struct term *t, const struct csi *csi); /* Screen alignment pattern (DECALN). */ void term_sc_decaln(struct term *t); /* Reset screen and cursor state for full reset (RIS). */ void term_sc_reset(struct term *t); /* Set reverse video mode (DECSCNM). */ void term_sc_set_reverse_video(struct term *t, bool enabled); /* Set autowrap mode (DECAWM). */ void term_sc_set_autowrap(struct term *t, bool enabled); /* Set cursor style (DECSCUSR). */ void term_sc_set_cursor_style(struct term *t, int ps); /* Bulk streaming of ASCII lines with raw rows and skip-ahead. */ size_t term_sc_bulk_ascii_lines(struct term *t, const char *buf, size_t len); /* Fast CSI parser. */ int term_try_fast_csi(struct term *t, const char *buf, size_t rem); static inline struct span sc_span_begin(struct term *t, int want) { (void)want; if (TERM_EXPECT(t->pending_wrap, 0)) { row_set_wrapped(term_row(t, t->cursor_y), true); t->cursor_x = t->lr_margin_mode ? t->left_margin : 0; sc_linefeed(t, 1); t->pending_wrap = false; } struct srow *row = term_row(t, t->cursor_y); if (TERM_EXPECT(row->kind != ROW_CELLS, 0)) srow_settle(row, t->cols); int cx = t->cursor_x; term_row_fill_gap(row, cx); int len = row->len; if (TERM_EXPECT(cx < len && (row->cells[cx].flags & CELL_TRAIL), 0)) fill_cells(row->cells + cx - 1, 1, row->cells[cx - 1].fg, row->cells[cx - 1].bg); int right = TERM_EXPECT(t->lr_margin_mode, 0) ? t->right_margin : t->cols - 1; int avail = right + 1 > cx ? right + 1 - cx : 0; return (struct span){ .row = row, .cells = row->cells + cx, .avail = avail, .x = cx, .old_len = len, }; } static inline void sc_span_end(struct term *t, struct span *s, int cols, uint8_t row_flags, uint32_t last) { struct srow *row = s->row; int end_x = s->x + cols; if (TERM_EXPECT(end_x < s->old_len && (row->cells[end_x].flags & CELL_TRAIL), 0)) fill_cells(row->cells + end_x, 1, row->cells[end_x].fg, row->cells[end_x].bg); if (end_x > s->old_len) row->len = (uint16_t)end_x; if (TERM_EXPECT(row_flags != 0, 0)) row->flags |= row_flags; row->version++; t->last_char = last; if (TERM_EXPECT(cols < s->avail, 1)) { t->cursor_x = end_x; t->pending_wrap = false; } else { t->cursor_x = s->x + s->avail - 1; t->pending_wrap = t->autowrap; } } /* Move the cursor after cols columns written at x, of avail. */ static inline void sc_text_advance(struct term *t, int x, int cols, int avail) { if (cols < avail) { t->cursor_x = x + cols; t->pending_wrap = false; } else { t->cursor_x = x + avail - 1; t->pending_wrap = t->autowrap; } } /* Append ASCII at the cursor, with the pen fg, bg, sl (flags | link << 16), * when the cursor row takes text. Return the bytes taken. */ static inline int sc_text_ascii(struct term *t, const char *s, int n, uint32_t fg, uint32_t bg, uint32_t sl) { if (TERM_EXPECT(t->insert_mode || t->lr_margin_mode, 0)) return 0; if (t->pending_wrap) { row_set_wrapped(term_row(t, t->cursor_y), true); t->cursor_x = 0; sc_linefeed(t, 1); t->pending_wrap = false; } struct srow *row = text_append_row(t); if (!row) return 0; int x = t->cursor_x, avail = t->cols - x; int take = n < avail ? n : avail; if (take <= 0) return 0; text_append(row, (const uint8_t *)s, take, take, take, HLINE_NARROW, fg, bg, sl); t->last_char = (unsigned char)s[take - 1]; sc_text_advance(t, x, take, avail); return take; } static inline void sc_print_ascii(struct term *t, const char *s, int n) { uint32_t fg = TERM_EXPECT(t->inverse, 0) ? t->cur_bg : t->cur_fg; uint32_t bg = TERM_EXPECT(t->inverse, 0) ? t->cur_fg : t->cur_bg; uint32_t flags = term_flags_pad(t); for (int k; n > 0 && (k = sc_text_ascii(t, s, n, fg, bg, flags)) > 0; s += k, n -= k) {} while (n > 0) { struct span span = sc_span_begin(t, n); int take = n < span.avail ? n : span.avail; if (TERM_EXPECT(take <= 0, 0)) { t->pending_wrap = t->autowrap; if (!t->autowrap) break; span = sc_span_begin(t, n); take = n < span.avail ? n : span.avail; if (take <= 0) break; } put_ascii_chunk(span.cells, s, take, fg, bg, flags); sc_span_end(t, &span, take, 0, (unsigned char)s[take - 1]); if (TERM_EXPECT(n <= take, 1)) break; s += take; n -= take; } } /* Write a short ASCII span without settling an already writable row. */ static inline bool sc_ascii_fast(struct term *t, const char *s, int n, uint32_t fg, uint32_t bg, uint32_t flags_pad) { int cx = t->cursor_x; if (TERM_EXPECT(t->pending_wrap || t->insert_mode || t->lr_margin_mode || t->inverse || n <= 0 || n > t->cols - cx, 0)) return false; struct srow *row = term_row(t, t->cursor_y); if (row->kind == ROW_TEXT || (row->len == 0 && cx == 0)) { if (sc_text_ascii(t, s, n, fg, bg, flags_pad) == n) return true; if (row->kind == ROW_TEXT) return false; } if (TERM_EXPECT(row->kind != ROW_CELLS || cx > row->len, 0)) return false; int len = row->len; struct cell *cells = row->cells; if (TERM_EXPECT((cx < len && (cells[cx].flags & CELL_TRAIL)) || (cx + n < len && (cells[cx + n].flags & CELL_TRAIL)), 0)) return false; put_ascii_chunk(cells + cx, s, n, fg, bg, flags_pad); cx += n; if (cx > len) row->len = (uint16_t)cx; row->version++; t->last_char = (unsigned char)s[n - 1]; if (cx < t->cols) { t->cursor_x = cx; t->pending_wrap = false; } else { t->cursor_x = t->cols - 1; t->pending_wrap = t->autowrap; } return true; } /* Fast inline check for 1-character write (fg-char fast path). */ static inline bool sc_put1_fast(struct term *t, char ch, uint32_t fg, uint32_t bg, uint32_t flags_pad) { int cx = t->cursor_x; int cols = t->cols; if (TERM_EXPECT(t->pending_wrap | t->insert_mode | t->lr_margin_mode | t->inverse, 0)) return false; if (TERM_EXPECT(cx >= cols, 0)) return false; struct srow *row = term_row(t, t->cursor_y); if (row->kind == ROW_TEXT) { struct text_row *tr = &row->text; if (TERM_EXPECT(cx != tr->cells || !tr->cells || cx + 1 == cols, 0)) return sc_text_ascii(t, &ch, 1, fg, bg, flags_pad) == 1; /* One character in the middle of a text row. */ tr->text[tr->text_len++] = (uint8_t)ch; tr->cells++; hist_runs_add_pen(&tr->runs, fg, bg, flags_pad, 1); if (flags_pad >> 16) tr->flags |= HLINE_HAS_LINK; if (tr->cells > row->len) row->len = tr->cells; row->version++; t->last_char = (uint8_t)ch; t->cursor_x = cx + 1; return true; } if (row->len == 0 && cx == 0) { if (sc_text_ascii(t, &ch, 1, fg, bg, flags_pad) == 1) return true; } int len = row->len; if (TERM_EXPECT(cx > len, 0)) return false; struct cell *cells = row->cells; if (TERM_EXPECT((cx < len && (cells[cx].flags & CELL_TRAIL)) || (cx + 1 < len && (cells[cx + 1].flags & CELL_TRAIL)), 0)) return false; cells[cx] = (struct cell){ (uint8_t)ch, fg, bg, (uint16_t)flags_pad, (uint16_t)(flags_pad >> 16) }; t->last_char = (uint8_t)ch; cx++; if (cx > len) row->len = (uint16_t)cx; row->version++; if (TERM_EXPECT(cx < cols, 1)) { t->cursor_x = cx; } else { t->cursor_x = cols - 1; t->pending_wrap = t->autowrap; } return true; } #endif