diff options
Diffstat (limited to 'src/internal.h')
| -rw-r--r-- | src/internal.h | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/src/internal.h b/src/internal.h new file mode 100644 index 0000000..dd4649c --- /dev/null +++ b/src/internal.h @@ -0,0 +1,411 @@ +#ifndef TERM_INTERNAL_H +#define TERM_INTERNAL_H + +#include "term.h" +#include "cell.h" +#include "csi.h" +#include "gstore.h" +#include "hist.h" +#include "hist_view.h" +#include "palette.h" +#include "pen.h" +#include "text_run.h" +#include "compat.h" +#include <stdatomic.h> +#include <stdlib.h> +#include <string.h> + +#define HASH64_BASIS UINT64_C(0x9E3779B97F4A7C15) +#define KNUTH_HASH_MULT UINT32_C(2654435761) + +static inline uint64_t hash64_step(uint64_t hash, uint32_t value) +{ + hash ^= value; + hash *= HASH64_BASIS; + return hash ^ (hash >> 29); +} + +static inline uint64_t hash64_finish(uint64_t hash) +{ + hash ^= hash >> 32; + hash *= UINT64_C(0xD6E8FEB86659FD93); + hash ^= hash >> 32; + return hash; +} + +#define KEYBOARD_STACK_SIZE 32 + +struct keyboard_mode { + unsigned flags, saved[KEYBOARD_STACK_SIZE], depth; +}; + +/* What DECSC saves, as in xterm: the cursor, the pen, origin mode, the + * pending wrap, and the character sets. */ +struct term_saved_cursor { + int x, y; + uint32_t fg, bg, ul; + char charsets[2]; + uint8_t underline; + uint8_t charset_active; + bool bold : 1; + bool faint : 1; + bool italic : 1; + bool strike : 1; + bool overline : 1; + bool inverse : 1; + bool origin : 1; + bool pending_wrap : 1; +}; + +/* + * A row stores cells, text, or default-pen ASCII. A text row uses the history + * format. An ASCII row stores only its pointer and length. A screen read or + * edit sets its text fields first. Other changes settle text into cells. + */ +enum { ROW_CELLS, ROW_TEXT, ROW_ASCII }; +enum { RF_WRAPPED = 1, RF_WIDE = 4, RF_CLUSTER = 8, RF_TAIL_PADDED = 16 }; + +struct srow { + struct cell *cells; /* stride >= cols, from a per-screen slab */ + uint8_t *ascii; + struct text_row text; /* TEXT: the content; len == text.cells, or cols with a wrap pad */ + uint64_t uid; /* new id when the row scrolls in or clears */ + uint32_t version; /* bumped on every change */ + uint16_t len; + uint8_t kind, flags; +}; + +struct screen { + struct srow *slot; + struct cell *cells_slab; + uint8_t *text_slab; + size_t cells_cap, text_cap; + /* Without history: the line of row 0, counted as rows leave the top. */ + uint64_t top_id; + int rows, cols, top; + int stride; + /* Capacity of the buffers above. A resize reuses them. */ + int slot_cap; + uint32_t top_seg; +}; + +struct term_sel_point { + uint64_t line_id; + uint32_t cell_off; +}; + +/* A buffer that only grows, so work that repeats does not allocate. */ +struct grow_buf { + void *data; + size_t cap; +}; + +/* What a resize needs besides the screens. It keeps its buffers, so a + * resize allocates nothing once the terminal has seen its sizes. */ +struct resize_scratch { + struct grow_buf row, rows, cells, line, pull, rest, lens; +}; + + +struct term { + _Atomic uint64_t seq; + _Atomic uint64_t writer_tid; + uint32_t write_depth; + int cols; + int rows; + int cursor_x; + int cursor_y; + int scroll_top, scroll_bottom; + + uint32_t cur_fg; + uint32_t cur_bg; + uint32_t cur_ul; + uint32_t last_char; + + uint32_t utf8_state; + uint32_t utf8_codep; + struct width_cache text_width; + + enum { ST_GROUND, ST_ESC, ST_CSI, ST_OSC, ST_DCS, ST_STR_ESC, ST_CHARSET, + ST_DCS_ESC, ST_IGNORE, ST_IGNORE_ESC, ST_DOCS, ST_DECALN } state; + struct csi_parser csi_parser; + + uint16_t cur_link; + char charsets[2]; + uint8_t underline; + uint8_t decsace; + char charset_designation; + bool graphics; + + bool bold : 1; + bool faint : 1; + bool italic : 1; + bool strike : 1; + bool overline : 1; + bool inverse : 1; + bool pending_wrap : 1; + bool autowrap : 1; + bool insert_mode : 1; + bool origin_mode : 1; + bool reverse_video : 1; + bool synchronized_output : 1; + bool alternate : 1; + bool application_cursor : 1; + bool application_keypad : 1; + bool lr_margin_mode : 1; + bool backarrow_sends_bs : 1; + bool focus_events : 1; + bool cursor_visible : 1; + bool cursor_blink : 1; + bool default_cursor_blink : 1; + bool selection : 1; + bool bracketed_paste : 1; + bool lnm : 1; + bool transport_newline : 1; + bool str_truncated : 1; + bool has_prompt : 1; + + struct screen scr; + struct screen alt_scr; + struct screen spare; /* the buffers a resize builds into */ + struct resize_scratch resize; + struct grow_buf ascii_ends; + struct grow_buf ascii_tail; + struct term_saved_cursor normal_saved_cursor; + struct term_saved_cursor normal_saved_decsc; + int normal_cols, normal_rows; + bool alt_allocated; + bool normal_cursor_visible; + uint8_t normal_cursor_style; + bool normal_cursor_blink; + + struct hist hist; + struct hist_view view; + uint64_t next_row_uid; + uint64_t full_gen; + int prompt_y; + int left_margin, right_margin; + struct term_sel_point sel_start, sel_end; + struct term_sel_point sel_anchor_first, sel_anchor_last; + enum term_select_unit select_unit; + + int mouse_mode, mouse_encoding; + enum term_cursor_style cursor_style; + enum term_cursor_style default_cursor_style; + int charset_active; + int osc_len; + int dcs_len; + int sb_capacity; + + struct keyboard_mode keyboard[2]; + /* The DECSC slot. Cold: it stays out of the hot fields above. */ + struct term_saved_cursor saved; + + uint8_t *tabs; + struct cell *view_row_scratch; + int tabs_cap; + int view_scratch_cap; + + char osc_buf[256]; + char tcap_name[64]; + + char *osc_heap; + size_t osc_heap_cap; + char *dcs_buf; + size_t dcs_cap; + void *links; + char *cwd; + char *pointer_shape; + + struct term_callbacks cb; + struct palette palette; + struct gstore *gstore; + /* Unique for the life of the process. A snapshot compares it, not the + * address, because a new terminal can reuse a freed one's memory. */ + uint64_t serial; +}; + +static inline void reset_sgr(struct term *t) +{ + t->cur_fg = TERM_COLOR_FG; + t->cur_bg = TERM_COLOR_BG; + t->bold = false; + t->faint = false; + t->italic = false; + t->underline = 0; + t->strike = false; + t->overline = false; + t->inverse = false; + t->cur_ul = 0; +} + +/* The DECSC slot before any save: home, the default pen, and ASCII. */ +static inline struct term_saved_cursor term_saved_cursor_initial(void) +{ + return (struct term_saved_cursor){ + .fg = TERM_COLOR_FG, .bg = TERM_COLOR_BG, .charsets = { 'B', 'B' }, + }; +} + +static inline struct term_saved_cursor term_cursor_save(const struct term *t) +{ + return (struct term_saved_cursor){ + .x = t->cursor_x, .y = t->cursor_y, + .fg = t->cur_fg, .bg = t->cur_bg, .ul = t->cur_ul, + .charsets = { t->charsets[0], t->charsets[1] }, + .underline = t->underline, + .charset_active = (uint8_t)t->charset_active, + .bold = t->bold, .faint = t->faint, .italic = t->italic, + .strike = t->strike, .overline = t->overline, .inverse = t->inverse, + .origin = t->origin_mode, .pending_wrap = t->pending_wrap, + }; +} + +/* Take the pen, origin mode, and character sets from s. The caller places + * the cursor. */ +static inline void term_cursor_apply(struct term *t, const struct term_saved_cursor *s) +{ + t->cur_fg = s->fg; + t->cur_bg = s->bg; + t->cur_ul = s->ul; + t->underline = s->underline; + t->bold = s->bold; + t->faint = s->faint; + t->italic = s->italic; + t->strike = s->strike; + t->overline = s->overline; + t->inverse = s->inverse; + t->origin_mode = s->origin; + t->charsets[0] = s->charsets[0]; + t->charsets[1] = s->charsets[1]; + t->charset_active = s->charset_active; + t->graphics = t->charsets[t->charset_active] == '0'; +} + +static inline void send_response(struct term *t, const char *str) +{ + if (t && t->cb.reply) + t->cb.reply(t->cb.ud, str, strlen(str)); +} +static inline int term_hex_nib(char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +static inline bool term_lnm(const struct term *t) +{ + return t->lnm || t->transport_newline; +} + +static inline bool mode_is_mouse(int m) +{ + return m == 9 || (m >= 1000 && m <= 1003); +} + +static inline bool mode_is_mouse_enc(int m) +{ + return m == 1005 || m == 1006 || m == 1015 || m == 1016; +} + +static const uint16_t term_ul_flags[] = { + 0, CELL_UNDERLINE, CELL_DOUBLE_UNDERLINE, + CELL_CURLY_UNDERLINE, CELL_DOTTED_UNDERLINE, + CELL_DASHED_UNDERLINE, +}; + +static inline uint16_t term_current_flags(const struct term *t) +{ + uint16_t f = 0; + if (t->bold) f |= CELL_BOLD; + if (t->faint) f |= CELL_FAINT; + if (t->italic) f |= CELL_ITALIC; + if (t->underline < sizeof(term_ul_flags) / sizeof(term_ul_flags[0])) + f |= term_ul_flags[t->underline]; + if (t->strike) f |= CELL_STRIKE; + if (t->overline) f |= CELL_OVERLINE; + if (t->cur_link) f |= CELL_LINK; + return f; +} + +static inline uint32_t term_flags_pad(const struct term *t) +{ + return term_current_flags(t) | ((uint32_t)t->cur_link << 16); +} + +static inline void term_tabs_clear(struct term *t) +{ + memset(t->tabs, 0, (size_t)t->tabs_cap); +} + +static inline void term_tabs_default(struct term *t) +{ + for (int c = 8; c < t->tabs_cap; c += 8) t->tabs[c] = 1; +} + +static inline bool term_str_was_truncated(struct term *t) +{ + bool truncated = t->str_truncated; + t->str_truncated = false; + return truncated; +} + +/* Copy text into buf with the rules of term_selection_text(). NULL text + * copies as empty. Return the full length. */ +static inline size_t term_copy_out(const char *text, char *buf, size_t cap) +{ + if (!text) + text = ""; + size_t n = strlen(text); + if (buf && cap) { + size_t k = n < cap ? n : cap - 1; + if (k) memcpy(buf, text, k); + buf[k] = '\0'; + } + return n; +} + +/* Every thread has its own anchor, so its address names the thread. */ +extern _Thread_local char term_thread_anchor; + +static inline uint64_t term_thread_id(void) +{ + return (uint64_t)(uintptr_t)&term_thread_anchor; +} + +static inline void term_write_begin(struct term *t) +{ + if (!t) return; + uint64_t self = term_thread_id(); + if (atomic_load_explicit(&t->writer_tid, memory_order_relaxed) == self) { + t->write_depth++; + return; + } + uint64_t s; + for (;;) { + s = atomic_load_explicit(&t->seq, memory_order_relaxed); + if (s & 1) { + term_cpu_relax(); + continue; + } + if (atomic_compare_exchange_weak_explicit(&t->seq, &s, s + 1, + memory_order_acquire, + memory_order_relaxed)) + break; + } + atomic_store_explicit(&t->writer_tid, self, memory_order_relaxed); + t->write_depth = 1; +} + +static inline void term_write_end(struct term *t) +{ + if (!t) return; + if (--t->write_depth == 0) { + atomic_store_explicit(&t->writer_tid, 0, memory_order_relaxed); + atomic_fetch_add_explicit(&t->seq, 1, memory_order_release); + } +} + +#endif |
