diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/compat.h | 75 | ||||
| -rw-r--r-- | src/core.c | 24 | ||||
| -rw-r--r-- | src/escape.c | 4 | ||||
| -rw-r--r-- | src/gstore.c | 28 | ||||
| -rw-r--r-- | src/hist.c | 14 | ||||
| -rw-r--r-- | src/input.c | 4 | ||||
| -rw-r--r-- | src/internal.h | 20 | ||||
| -rw-r--r-- | src/meson.build | 8 | ||||
| -rw-r--r-- | src/osc/misc.c | 2 | ||||
| -rw-r--r-- | src/screen.c | 47 | ||||
| -rw-r--r-- | src/selection.c | 10 | ||||
| -rw-r--r-- | src/snapshot.c | 26 | ||||
| -rw-r--r-- | src/term.c | 7 | ||||
| -rw-r--r-- | src/text.c | 52 | ||||
| -rw-r--r-- | src/text_run.c | 24 | ||||
| -rw-r--r-- | src/text_run.h | 18 |
16 files changed, 236 insertions, 127 deletions
diff --git a/src/compat.h b/src/compat.h index cb62679..6c3302f 100644 --- a/src/compat.h +++ b/src/compat.h @@ -14,6 +14,19 @@ #include <stdlib.h> #include <string.h> +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include <windows.h> +#else +#include <pthread.h> +#include <unistd.h> +#endif + #if defined(__GNUC__) || defined(__clang__) #define TERM_COMPILER_GNU 1 #elif defined(_MSC_VER) @@ -184,6 +197,68 @@ static inline void term_cpu_relax(void) #endif } +/* The writer lock. POSIX uses a mutex with priority inheritance where the + * platform has it, so a waiter lifts the thread that holds the lock. Windows + * uses an SRWLOCK. Neither nests, so term_write_begin() counts re-entry. */ +#if defined(_WIN32) +typedef SRWLOCK term_mutex; +#else +typedef pthread_mutex_t term_mutex; +/* Strict C11 can hide PTHREAD_PRIO_INHERIT in pthread.h, as zig's aarch64 + * glibc headers do. Every POSIX system that has the option uses 1 for it. */ +#if !defined(PTHREAD_PRIO_INHERIT) +#define PTHREAD_PRIO_INHERIT 1 +#endif +#endif + +/* Returns false when the lock cannot be made. */ +static inline bool term_mutex_init(term_mutex *m) +{ +#if defined(_WIN32) + InitializeSRWLock(m); + return true; +#elif defined(_POSIX_THREAD_PRIO_INHERIT) && _POSIX_THREAD_PRIO_INHERIT > 0 + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr) != 0) + return false; + bool ok = pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT) == 0 && + pthread_mutex_init(m, &attr) == 0; + pthread_mutexattr_destroy(&attr); + return ok; +#else + return pthread_mutex_init(m, NULL) == 0; +#endif +} + +/* A normal mutex fails only when it is broken, so the result is ignored. */ +static inline void term_mutex_lock(term_mutex *m) +{ +#if defined(_WIN32) + AcquireSRWLockExclusive(m); +#else + (void)pthread_mutex_lock(m); +#endif +} + +static inline void term_mutex_unlock(term_mutex *m) +{ +#if defined(_WIN32) + ReleaseSRWLockExclusive(m); +#else + (void)pthread_mutex_unlock(m); +#endif +} + +/* An SRWLOCK has no destroy. */ +static inline void term_mutex_destroy(term_mutex *m) +{ +#if defined(_WIN32) + (void)m; +#else + (void)pthread_mutex_destroy(m); +#endif +} + /* strdup is POSIX, so the core copies strings itself. */ static inline char *term_strdup(const char *s) { @@ -16,13 +16,13 @@ static uint16_t cluster_presentation(const uint32_t *cps, int len) { if (len <= 1) return 0; - bool pictographic = unicode_extended_pictographic(cps[0]); + bool pictographic = term_unicode_extended_pictographic(cps[0]); if (len == 2) { uint32_t c1 = cps[1]; if (c1 == 0xFE0F) return CELL_EMOJI; if (c1 == 0xFE0E) return CELL_TEXT; - if (c1 == 0x20E3 || grapheme_regional_indicator(c1) || - (pictographic && (c1 == 0x200D || grapheme_emoji_modifier(c1) || + if (c1 == 0x20E3 || term_grapheme_regional_indicator(c1) || + (pictographic && (c1 == 0x200D || term_grapheme_emoji_modifier(c1) || (c1 >= 0xE0020 && c1 <= 0xE007F)))) return CELL_EMOJI; return 0; @@ -33,8 +33,8 @@ static uint16_t cluster_presentation(const uint32_t *cps, int len) return CELL_EMOJI; if (cps[i] == 0xFE0E) return CELL_TEXT; - if (cps[i] == 0x20E3 || grapheme_regional_indicator(cps[i]) || - (pictographic && (cps[i] == 0x200D || grapheme_emoji_modifier(cps[i]) || + if (cps[i] == 0x20E3 || term_grapheme_regional_indicator(cps[i]) || + (pictographic && (cps[i] == 0x200D || term_grapheme_emoji_modifier(cps[i]) || (cps[i] >= 0xE0020 && cps[i] <= 0xE007F)))) emoji = true; } @@ -44,9 +44,9 @@ static uint16_t cluster_presentation(const uint32_t *cps, int len) void term_sc_print_glyph(struct term *t, uint32_t cp, int width, uint16_t flags) { uint32_t base = cp; - if (TERM_EXPECT(grapheme_is_handle(cp), 0)) { - uint32_t cps[GRAPHEME_MAX_LEN]; - int len = term_gstore_expand(t->gstore, cp, cps, GRAPHEME_MAX_LEN); + if (TERM_EXPECT(term_grapheme_is_handle(cp), 0)) { + uint32_t cps[TERM_GRAPHEME_MAX_LEN]; + int len = term_gstore_expand(t->gstore, cp, cps, TERM_GRAPHEME_MAX_LEN); base = cps[0]; flags = (flags & ~(CELL_EMOJI | CELL_TEXT)) | cluster_presentation(cps, len); } @@ -179,13 +179,13 @@ bool term_sc_extend_cluster(struct term *t, uint32_t cp, int klass) return false; struct cell *prev = last_written_cell(t); - uint32_t cps[GRAPHEME_MAX_LEN]; - int len = prev && prev->c > ' ' ? term_gstore_expand(t->gstore, prev->c, cps, GRAPHEME_MAX_LEN) : 0; + uint32_t cps[TERM_GRAPHEME_MAX_LEN]; + int len = prev && prev->c > ' ' ? term_gstore_expand(t->gstore, prev->c, cps, TERM_GRAPHEME_MAX_LEN) : 0; if (!len) return joins_cluster(cp, klass); if (!cluster_joins(cps, len, cp, klass)) return false; - if (len >= GRAPHEME_MAX_LEN) + if (len >= TERM_GRAPHEME_MAX_LEN) return true; cps[len++] = cp; @@ -517,7 +517,7 @@ static inline bool next_byte_wraps(const char *buf, size_t len, size_t pos, uint { if (pos >= len) return false; uint32_t cp; - if (!utf8_decode_one(buf + pos, len - pos, &cp)) return false; + if (!term_utf8_decode_one(buf + pos, len - pos, &cp)) return false; int w = term_text_class(cp); return w > 0 && !can_extend_cluster(last, cp, w); } diff --git a/src/escape.c b/src/escape.c index da0449a..dceb7a6 100644 --- a/src/escape.c +++ b/src/escape.c @@ -834,7 +834,9 @@ static inline TERM_ALWAYS_INLINE void dispatch_csi(struct term *t, const struct case 'q': { if (csi->prefix == '>' && !csi->intermediate && (!csi->nparams || !csi->params[0])) { - send_response(t, "\033P>|wsap_term\033\\"); + char resp[96]; + snprintf(resp, sizeof(resp), "\033P>|%s\033\\", t->name); + send_response(t, resp); } else if (!csi->prefix && csi->intermediate == ' ') { term_sc_set_cursor_style(t, csi->nparams > 0 ? csi->params[0] : 0); } diff --git a/src/gstore.c b/src/gstore.c index d2bc57f..813ea7d 100644 --- a/src/gstore.c +++ b/src/gstore.c @@ -72,11 +72,11 @@ int term_gstore_expand(const struct gstore *g, uint32_t handle, uint32_t *cps, i { if (cap <= 0) return 0; - if (!g || !grapheme_is_handle(handle)) { + if (!g || !term_grapheme_is_handle(handle)) { cps[0] = handle; return 1; } - uint32_t idx = handle - GRAPHEME_HANDLE_BASE; + uint32_t idx = handle - TERM_GRAPHEME_HANDLE_BASE; if (idx >= g->entries_count || !g->entries[idx].allocated) { cps[0] = 0xFFFD; return 1; @@ -124,8 +124,8 @@ static bool grow_slots(struct gstore *g) struct gentry *e = &g->entries[i]; if (!e->allocated) continue; - uint32_t cps_buf[GRAPHEME_MAX_LEN]; - int n = term_gstore_expand(g, GRAPHEME_HANDLE_BASE + i, cps_buf, GRAPHEME_MAX_LEN); + uint32_t cps_buf[TERM_GRAPHEME_MAX_LEN]; + int n = term_gstore_expand(g, TERM_GRAPHEME_HANDLE_BASE + i, cps_buf, TERM_GRAPHEME_MAX_LEN); uint64_t h = hash_cps(cps_buf, n); uint32_t h32 = (uint32_t)h ? (uint32_t)h : 1; gstore_insert_slot(g, h32, i); @@ -147,7 +147,7 @@ static uint32_t gstore_find(const struct gstore *g, const uint32_t *cps, int n, uint32_t e_idx = slot->entry_idx - 1; const struct gentry *e = &g->entries[e_idx]; if (e->allocated && gentry_matches(g, e, cps, n)) - return GRAPHEME_HANDLE_BASE + e_idx; + return TERM_GRAPHEME_HANDLE_BASE + e_idx; } idx = (idx + 1) & mask; } @@ -193,9 +193,9 @@ void term_gstore_unpin(struct gstore *g) void term_gstore_mark(struct gstore *g, uint32_t handle) { - if (!g || !grapheme_is_handle(handle)) + if (!g || !term_grapheme_is_handle(handle)) return; - uint32_t idx = handle - GRAPHEME_HANDLE_BASE; + uint32_t idx = handle - TERM_GRAPHEME_HANDLE_BASE; if (idx < g->entries_count && g->entries[idx].allocated) g->entries[idx].mark = true; } @@ -204,7 +204,7 @@ static void sweep_mark_row(struct gstore *g, const struct cell *cells, int len) { for (int x = 0; x < len; x++) { uint32_t c = cells[x].c; - if (grapheme_is_handle(c)) + if (term_grapheme_is_handle(c)) term_gstore_mark(g, c); } } @@ -215,7 +215,7 @@ void term_gstore_sweep(struct gstore *g, struct term *t) return; if (t) { - if (grapheme_is_handle(t->last_char)) + if (term_grapheme_is_handle(t->last_char)) term_gstore_mark(g, t->last_char); struct screen *s = &t->scr; @@ -266,8 +266,8 @@ void term_gstore_sweep(struct gstore *g, struct term *t) live++; e->mark = false; if (g->slots) { - uint32_t cps_buf[GRAPHEME_MAX_LEN]; - int n = term_gstore_expand(g, GRAPHEME_HANDLE_BASE + i, cps_buf, GRAPHEME_MAX_LEN); + uint32_t cps_buf[TERM_GRAPHEME_MAX_LEN]; + int n = term_gstore_expand(g, TERM_GRAPHEME_HANDLE_BASE + i, cps_buf, TERM_GRAPHEME_MAX_LEN); uint64_t h = hash_cps(cps_buf, n); uint32_t h32 = (uint32_t)h ? (uint32_t)h : 1; gstore_insert_slot(g, h32, i); @@ -300,8 +300,8 @@ uint32_t term_gstore_intern(struct gstore *g, const uint32_t *cps, int n) return 0; if (n == 1) return cps[0]; - if (n > GRAPHEME_MAX_LEN) - n = GRAPHEME_MAX_LEN; + if (n > TERM_GRAPHEME_MAX_LEN) + n = TERM_GRAPHEME_MAX_LEN; uint64_t h = hash_cps(cps, n); uint32_t h32 = (uint32_t)h ? (uint32_t)h : 1; @@ -375,5 +375,5 @@ uint32_t term_gstore_intern(struct gstore *g, const uint32_t *cps, int n) gstore_insert_slot(g, h32, entry_idx); g->live_count++; - return GRAPHEME_HANDLE_BASE + entry_idx; + return TERM_GRAPHEME_HANDLE_BASE + entry_idx; } @@ -42,7 +42,7 @@ #define ROWS_UNKNOWN 255 /* Bytes one cell can need, except a cluster. */ #define CELL_BYTES 5 -#define CLUSTER_BYTES (2 + GRAPHEME_MAX_LEN * 4) +#define CLUSTER_BYTES (2 + TERM_GRAPHEME_MAX_LEN * 4) /* ---- Parts and pages ---- */ @@ -1039,8 +1039,8 @@ static int uniform_utf8_prefix(const struct cell *cells, int len, uint8_t *dst, static TERM_NOINLINE size_t encode_cluster(uint8_t *dst, const struct gstore *g, uint32_t handle, uint16_t f) { - uint32_t cps[GRAPHEME_MAX_LEN]; - int n = term_gstore_expand(g, handle, cps, GRAPHEME_MAX_LEN); + uint32_t cps[TERM_GRAPHEME_MAX_LEN]; + int n = term_gstore_expand(g, handle, cps, TERM_GRAPHEME_MAX_LEN); size_t b = 0; dst[b++] = PREFIX_CLUSTER; dst[b++] = (uint8_t)((n & 0x1f) | ((f & CELL_WIDE) ? 0x80 : 0) | @@ -1053,7 +1053,7 @@ static TERM_NOINLINE size_t encode_cluster(uint8_t *dst, const struct gstore *g, static size_t encode_shaped(uint8_t *dst, uint32_t cp, uint16_t f, const struct gstore *g, uint8_t *flags) { - if (grapheme_is_handle(cp)) { + if (term_grapheme_is_handle(cp)) { *flags |= HLINE_HAS_CLUSTER | ((f & CELL_WIDE) ? HLINE_HAS_WIDE : 0); return encode_cluster(dst, g, cp, f); } @@ -1155,7 +1155,7 @@ static int encode_cells(struct hist_runs *r, const struct cell *cells, int n, ui continue; uint32_t cp = c->c; bool simple = !(f & (CELL_WIDE | CELL_EMOJI | CELL_TEXT)) && cp - 0x20u < 0x60u; - if (!simple && grapheme_is_handle(cp) && + if (!simple && term_grapheme_is_handle(cp) && bytes + CLUSTER_BYTES + (size_t)(n - i) * CELL_BYTES > cap) break; uint32_t sl = (uint32_t)(f & STYLE_FLAGS) | ((uint32_t)c->pad << 16); @@ -2516,12 +2516,12 @@ static const uint8_t *text_cell(const uint8_t *s, const uint8_t *end, struct gst } uint8_t k = s[1]; s += 2; - uint32_t cps[GRAPHEME_MAX_LEN]; + uint32_t cps[TERM_GRAPHEME_MAX_LEN]; int n = 0; for (int i = 0; i < (k & 0x1f) && s < end; i++) { uint32_t c; s += get_utf8(s, end, &c); - if (n < GRAPHEME_MAX_LEN) + if (n < TERM_GRAPHEME_MAX_LEN) cps[n++] = c; } *cp = n == 0 ? ' ' : (g && n > 1) ? term_gstore_intern(g, cps, n) : cps[0]; diff --git a/src/input.c b/src/input.c index 9fee179..2f0cd5c 100644 --- a/src/input.c +++ b/src/input.c @@ -53,7 +53,7 @@ static inline void sb_u32(struct seqbuf *sb, uint32_t val) static inline void sb_utf8(struct seqbuf *sb, uint32_t cp) { char tmp[8]; - size_t n = (size_t)utf8_encode(cp, tmp); + size_t n = (size_t)term_utf8_encode(cp, tmp); sb_bytes(sb, tmp, n); } @@ -695,7 +695,7 @@ struct term_input_result term_input_encode_paste(const struct term_modes *m, continue; } uint32_t cp; - int n = utf8_decode_one(text + i, len - i, &cp); + int n = term_utf8_decode_one(text + i, len - i, &cp); if (!n) { i++; continue; } if (cp >= 0xa0 && !is_direction_format(cp)) { sb_bytes(&sb, text + i, (size_t)n); diff --git a/src/internal.h b/src/internal.h index 8c02c91..676413c 100644 --- a/src/internal.h +++ b/src/internal.h @@ -108,7 +108,8 @@ struct resize_scratch { struct term { - _Atomic uint64_t seq; + /* The writer section. A waiting thread sleeps on it. */ + term_mutex write_lock; _Atomic uint64_t writer_tid; uint32_t write_depth; int cols; @@ -208,6 +209,8 @@ struct term { bool custom_tabs; char osc_buf[256]; + /* The host's name. A DCS sequence can change tcap_name, not this. */ + char name[64]; char tcap_name[64]; char *osc_heap; @@ -375,18 +378,7 @@ static inline void term_write_begin(struct term *t) 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; - } + term_mutex_lock(&t->write_lock); atomic_store_explicit(&t->writer_tid, self, memory_order_relaxed); t->write_depth = 1; } @@ -396,7 +388,7 @@ 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); + term_mutex_unlock(&t->write_lock); } } diff --git a/src/meson.build b/src/meson.build index 694ee0e..7684275 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,9 @@ subdir('osc') +# The SONAME carries the major version. An ABI break bumps it, so an old +# binary keeps the old library. +term_soversion = meson.project_version().split('.')[0] + # The x86-64 SIMD kernels pick their level at run time (see simd.h), so the # library needs no ISA flags. -march in c_args sets the lowest level. term_lib = both_libraries('term', @@ -21,8 +25,10 @@ term_lib = both_libraries('term', 'text_run.c', term_osc_src, include_directories: term_private_inc, - dependencies: [m_dep], + dependencies: [m_dep, thread_dep], gnu_symbol_visibility: 'hidden', + version: meson.project_version(), + soversion: term_soversion, install: true ) diff --git a/src/osc/misc.c b/src/osc/misc.c index 5d6b506..74b35cf 100644 --- a/src/osc/misc.c +++ b/src/osc/misc.c @@ -53,7 +53,7 @@ void term_osc_misc_dispatch(struct term *t, long code, const char *params) break; case 9: if (t->cb.notify && strncmp(params, "4;", 2)) - t->cb.notify(t->cb.ud, "wsap_term", params); + t->cb.notify(t->cb.ud, t->name, params); break; case 22: free(t->pointer_shape); diff --git a/src/screen.c b/src/screen.c index 87d6f2c..b99948d 100644 --- a/src/screen.c +++ b/src/screen.c @@ -808,12 +808,11 @@ void term_screen_set_alternate(struct term *t, bool enabled) } } +/* The caller has checked that requested is not negative. */ static int scrollback_lines(int requested) { if (requested == 0) return TERM_SCROLLBACK_DEFAULT; - if (requested < 0) - return 0; return requested > 1000000 ? 1000000 : requested; } @@ -841,22 +840,45 @@ bool term_tables_ready(void) return atomic_load_explicit(&tables_state, memory_order_relaxed) == 2; } +/* A name is 1 to 63 printable ASCII bytes. DCS replies echo it. */ +static bool name_valid(const char *s) +{ + for (size_t i = 0; s[i]; i++) { + unsigned char c = (unsigned char)s[i]; + if (i >= 63 || c < 0x20 || c > 0x7e) + return false; + } + return true; +} + struct term *term_new(const struct term_options *opt) { tables_init(); static const struct term_options defaults; if (!opt) opt = &defaults; - int cols = opt->size.x < 1 ? 80 : opt->size.x; - int rows = opt->size.y < 1 ? 24 : opt->size.y; - if (cols > TERM_MAX_COLS) - cols = TERM_MAX_COLS; + for (size_t i = 0; i < sizeof(opt->reserved) / sizeof(opt->reserved[0]); i++) + if (opt->reserved[i]) + return NULL; + if (opt->size.x < 0 || opt->size.y < 0 || opt->size.x > TERM_MAX_COLS) + return NULL; + if (opt->scrollback < 0 || (unsigned)opt->cursor_style > TERM_CURSOR_BAR) + return NULL; + const char *name = opt->name && *opt->name ? opt->name : "term"; + if (!name_valid(name)) + return NULL; + int cols = opt->size.x == 0 ? 80 : opt->size.x; + int rows = opt->size.y == 0 ? 24 : opt->size.y; static _Atomic uint64_t next_serial = 1; struct term *t = calloc(1, sizeof(*t)); if (!t) return NULL; - atomic_init(&t->seq, 0); + if (!term_mutex_init(&t->write_lock)) { + free(t); + return NULL; + } + memcpy(t->name, name, strlen(name) + 1); atomic_init(&t->writer_tid, 0); t->write_depth = 0; t->serial = atomic_fetch_add_explicit(&next_serial, 1, memory_order_relaxed); @@ -871,11 +893,13 @@ struct term *term_new(const struct term_options *opt) t->next_row_uid = 1; if (!screen_setup(&t->scr, cols, rows, &t->next_row_uid)) { + term_mutex_destroy(&t->write_lock); free(t); return NULL; } if (!term_hist_init(&t->hist, t->sb_capacity, 0)) { screen_free(&t->scr); + term_mutex_destroy(&t->write_lock); free(t); return NULL; } @@ -895,14 +919,14 @@ struct term *term_new(const struct term_options *opt) t->cur_fg = TERM_COLOR_FG; t->cur_bg = TERM_COLOR_BG; t->cursor_visible = true; - t->default_cursor_style = opt->cursor_style > TERM_CURSOR_BAR ? TERM_CURSOR_BLOCK : opt->cursor_style; + t->default_cursor_style = opt->cursor_style; t->default_cursor_blink = !opt->cursor_steady; t->cursor_style = t->default_cursor_style; t->cursor_blink = t->default_cursor_blink; t->autowrap = true; t->charsets[0] = t->charsets[1] = 'B'; t->saved = term_saved_cursor_initial(); - memcpy(t->tcap_name, "wsap_term", sizeof("wsap_term")); + memcpy(t->tcap_name, t->name, sizeof(t->name)); term_tabs_default(t); t->full_gen = 1; memset(t->text_width.lo, 0xFF, sizeof(t->text_width.lo)); @@ -932,6 +956,7 @@ void term_free(struct term *t) free(t->tabs); term_osc_cleanup(t); term_gstore_free(t->gstore); + term_mutex_destroy(&t->write_lock); free(t); } @@ -1523,10 +1548,8 @@ bool term_resize(struct term *t, struct term_vector2i size) return false; int cols = size.x; int rows = size.y; - if (cols < 1 || rows < 1) + if (cols < 1 || rows < 1 || cols > TERM_MAX_COLS) return false; - if (cols > TERM_MAX_COLS) - cols = TERM_MAX_COLS; term_write_begin(t); if (cols == t->cols && rows == t->rows) { term_write_end(t); diff --git a/src/selection.c b/src/selection.c index 3ac2ee3..40fb1f1 100644 --- a/src/selection.c +++ b/src/selection.c @@ -742,17 +742,17 @@ static char *range_text_inner(const struct term *t, uint32_t cp = cells[c].c; if (cells[c].flags & (CELL_TRAIL | CELL_WRAP_PAD)) continue; - /* One cell expands to GRAPHEME_MAX_LEN code points of up + /* One cell expands to TERM_GRAPHEME_MAX_LEN code points of up * to 4 bytes each. Leave room for all of them. */ - if (!text_reserve(&text, &cap, n + (size_t)GRAPHEME_MAX_LEN * 4 + 1)) + if (!text_reserve(&text, &cap, n + (size_t)TERM_GRAPHEME_MAX_LEN * 4 + 1)) break; if (cp > 0 && cp < 128) { text[n++] = (char)cp; } else { - uint32_t cps[GRAPHEME_MAX_LEN]; - int len = term_gstore_expand(t->gstore, cp ? cp : ' ', cps, GRAPHEME_MAX_LEN); + uint32_t cps[TERM_GRAPHEME_MAX_LEN]; + int len = term_gstore_expand(t->gstore, cp ? cp : ' ', cps, TERM_GRAPHEME_MAX_LEN); for (int k = 0; k < len; k++) - n += (size_t)utf8_encode(cps[k], text + n); + n += (size_t)term_utf8_encode(cps[k], text + n); } } if (text && !is_last && !wrapped && text_reserve(&text, &cap, n + 2)) diff --git a/src/snapshot.c b/src/snapshot.c index 82a2805..8726265 100644 --- a/src/snapshot.c +++ b/src/snapshot.c @@ -78,8 +78,8 @@ static bool reserve(struct term_snapshot *s, int cols, int rows) /* Append a cluster record. The caller reserved room for it. */ static uint32_t arena_add(struct term_snapshot *s, const struct gstore *g, uint32_t handle) { - uint32_t cps[GRAPHEME_MAX_LEN]; - int n = term_gstore_expand(g, handle, cps, GRAPHEME_MAX_LEN); + uint32_t cps[TERM_GRAPHEME_MAX_LEN]; + int n = term_gstore_expand(g, handle, cps, TERM_GRAPHEME_MAX_LEN); if (n <= 0) return ' '; uint32_t off = s->arena_len; @@ -92,13 +92,13 @@ static uint32_t arena_add(struct term_snapshot *s, const struct gstore *g, uint3 /* Arena words that converting src adds. Counts what arena_add() writes. */ static uint64_t row_words(const struct gstore *g, const struct cell *src, int cols) { - uint32_t cps[GRAPHEME_MAX_LEN]; + uint32_t cps[TERM_GRAPHEME_MAX_LEN]; uint64_t words = 0; for (int x = 0; x < cols; x++) { uint32_t text = src[x].c; - if (!grapheme_is_handle(text)) + if (!term_grapheme_is_handle(text)) continue; - int n = term_gstore_expand(g, text, cps, GRAPHEME_MAX_LEN); + int n = term_gstore_expand(g, text, cps, TERM_GRAPHEME_MAX_LEN); if (n > 0) words += (uint64_t)n + 1; } @@ -117,7 +117,7 @@ static uint32_t convert_row(struct term_snapshot *s, const struct gstore *g, uint32_t text = c->c; if (TERM_EXPECT(text < 0x20, 0)) text = text ? ' ' : 0; - else if (TERM_EXPECT(grapheme_is_handle(text), 0)) + else if (TERM_EXPECT(term_grapheme_is_handle(text), 0)) text = arena_add(s, g, text); dst[x] = (struct term_cell){ .text = text, @@ -341,34 +341,36 @@ bool term_snapshot_update(struct term_snapshot *s, struct term *t) const struct term_view *term_snapshot_view(const struct term_snapshot *s) { - return &s->view; + return s ? &s->view : NULL; } const struct term_cell *term_snapshot_row(const struct term_snapshot *s, int y) { - if (y < 0 || y >= s->view.size.y) + if (!s || y < 0 || y >= s->view.size.y) return NULL; return s->cells + (size_t)y * s->view.size.x; } uint64_t term_snapshot_row_key(const struct term_snapshot *s, int y) { - if (y < 0 || y >= s->view.size.y) + if (!s || y < 0 || y >= s->view.size.y) return 0; return s->keys[y]; } struct term_span term_snapshot_row_selection(const struct term_snapshot *s, int y) { - if (y < 0 || y >= s->view.size.y) + if (!s || y < 0 || y >= s->view.size.y) return (struct term_span){ 0 }; return s->selection[y]; } int term_snapshot_cluster(const struct term_snapshot *s, uint32_t text, uint32_t *cps, int cap) { + if (!s) + return 0; if (!(text & TERM_TEXT_CLUSTER)) { - if (cap > 0) + if (cps && cap > 0) cps[0] = text; return 1; } @@ -377,7 +379,7 @@ int term_snapshot_cluster(const struct term_snapshot *s, uint32_t text, uint32_t return 0; int n = (int)s->arena[off]; int copy = n < cap ? n : cap; - if (copy > 0) + if (cps && copy > 0) memcpy(cps, &s->arena[off + 1], (size_t)copy * sizeof(*cps)); return n; } @@ -5,7 +5,12 @@ _Thread_local char term_thread_anchor; const char *term_unicode_version(void) { - return UNICODE_DATA_VERSION; + return TERM_UNICODE_DATA_VERSION; +} + +unsigned term_version(void) +{ + return TERM_VERSION_CODE; } void term_lock(struct term *t) @@ -50,15 +50,15 @@ static inline void emit_decoded(struct term *t, uint32_t cp, uint16_t flags) * sequence prints U+FFFD (Unicode 3.9, maximal subparts). */ void term_text_utf8_interrupt(struct term *t) { - if (TERM_EXPECT(t->utf8_state == UTF8_ACCEPT, 1)) + if (TERM_EXPECT(t->utf8_state == TERM_UTF8_ACCEPT, 1)) return; - t->utf8_state = UTF8_ACCEPT; + t->utf8_state = TERM_UTF8_ACCEPT; emit_decoded(t, 0xFFFD, term_current_flags(t)); } void term_text_ground(struct term *t, unsigned char ch) { - if (ch >= 0x20 && ch < 0x7f && t->utf8_state == UTF8_ACCEPT) { + if (ch >= 0x20 && ch < 0x7f && t->utf8_state == TERM_UTF8_ACCEPT) { emit_codepoint(t, ch, 1, term_current_flags(t)); return; } @@ -92,14 +92,14 @@ void term_text_ground(struct term *t, unsigned char ch) t->graphics = t->charsets[0] == '0'; } else if (ch >= 0x20 && ch != 0x7f) { uint32_t before = t->utf8_state; - uint32_t st = utf8_decode(&t->utf8_state, &t->utf8_codep, ch); - if (st == UTF8_ACCEPT) { + uint32_t st = term_utf8_decode(&t->utf8_state, &t->utf8_codep, ch); + if (st == TERM_UTF8_ACCEPT) { emit_decoded(t, t->utf8_codep, term_current_flags(t)); - } else if (st == UTF8_REJECT) { - t->utf8_state = UTF8_ACCEPT; + } else if (st == TERM_UTF8_REJECT) { + t->utf8_state = TERM_UTF8_ACCEPT; emit_decoded(t, 0xFFFD, term_current_flags(t)); /* The byte that ended an unfinished sequence starts anew. */ - if (before != UTF8_ACCEPT) + if (before != TERM_UTF8_ACCEPT) term_text_ground(t, ch); } } @@ -109,7 +109,7 @@ static inline bool can_stream_bottom(const struct term *t) { return t->state == ST_GROUND && !t->graphics && !t->insert_mode && t->autowrap && !t->lr_margin_mode && - t->utf8_state == UTF8_ACCEPT && !t->inverse && + t->utf8_state == TERM_UTF8_ACCEPT && !t->inverse && t->scroll_top == 0 && t->scroll_bottom == t->rows - 1 && t->cursor_y == t->rows - 1 && !t->alternate && t->sb_capacity > 0 && @@ -123,14 +123,14 @@ static inline bool try_text_run(struct term *t, const char *buf, size_t len, if (t->pending_wrap || i + 4 > len || ((unsigned char)buf[i] >= 0x80 && ((unsigned char)buf[i] < 0xc2 || (unsigned char)buf[i] > 0xf4)) || - t->last_char == 0x200D || grapheme_is_handle(t->last_char)) + t->last_char == 0x200D || term_grapheme_is_handle(t->last_char)) return false; if (t->cursor_x != 0 && t->cursor_x != term_row(t, t->cursor_y)->len) return false; if ((unsigned char)buf[i] >= 0x80) { uint32_t cp; - int bytes = utf8_decode_one(buf + i, len - i, &cp); + int bytes = term_utf8_decode_one(buf + i, len - i, &cp); if (!bytes || term_text_class(cp) <= 0) return false; } @@ -181,7 +181,7 @@ static inline bool try_text_run(struct term *t, const char *buf, size_t len, newline = true; } if (!newline || i + 4 > len || t->pending_wrap || - t->last_char == 0x200D || grapheme_is_handle(t->last_char) || + t->last_char == 0x200D || term_grapheme_is_handle(t->last_char) || (t->cursor_x != 0 && t->cursor_x != term_row(t, t->cursor_y)->len)) break; @@ -190,10 +190,11 @@ static inline bool try_text_run(struct term *t, const char *buf, size_t len, return wrote; } +/* The feed flags this build knows. term_feed() rejects any other bit. */ +#define TERM_FEED_KNOWN TERM_FEED_ONLCR + static void term_feed_inner(struct term *t, const char *buf, size_t len, unsigned flags) { - if (!t || !buf || len == 0) - return; t->transport_newline = (flags & TERM_FEED_ONLCR) != 0; uint16_t cur_flags = term_current_flags(t); @@ -212,7 +213,7 @@ static void term_feed_inner(struct term *t, const char *buf, size_t len, unsigne unsigned char ch = (unsigned char)buf[i]; if (TERM_EXPECT(ch >= 0x20 && ch < 0x7f && !t->insert_mode && !t->lr_margin_mode && t->state == ST_GROUND && !t->graphics && - t->utf8_state == UTF8_ACCEPT, 1)) { + t->utf8_state == TERM_UTF8_ACCEPT, 1)) { int avail = t->pending_wrap ? t->cols : t->cols - t->cursor_x; int rem = (int)(len - i); if (rem > avail) rem = avail; @@ -239,7 +240,7 @@ static void term_feed_inner(struct term *t, const char *buf, size_t len, unsigne (unsigned char)buf[i + 1] <= 0xf4 && (i + 2 == len || ((unsigned char)buf[i + 2] & 0xc0) == 0x80)) { uint32_t next_cp; - if (utf8_decode_one(buf + i + 1, len - i - 1, &next_cp) && + if (term_utf8_decode_one(buf + i + 1, len - i - 1, &next_cp) && term_text_class(next_cp) > 0 && try_text_run(t, buf, len, &i)) continue; @@ -249,7 +250,7 @@ static void term_feed_inner(struct term *t, const char *buf, size_t len, unsigne } if (i + (size_t)run < len && (unsigned char)buf[i + run] >= 0x80) { uint32_t next_cp; - if (utf8_decode_one(buf + i + run, len - i - (size_t)run, &next_cp) && + if (term_utf8_decode_one(buf + i + run, len - i - (size_t)run, &next_cp) && term_text_class(next_cp) > 0 && try_text_run(t, buf, len, &i)) continue; @@ -274,21 +275,21 @@ static void term_feed_inner(struct term *t, const char *buf, size_t len, unsigne if (TERM_EXPECT(ch >= 0x80, 1)) { if (TERM_EXPECT(t->state == ST_GROUND && - t->utf8_state == UTF8_ACCEPT && !t->graphics, 1)) { + t->utf8_state == TERM_UTF8_ACCEPT && !t->graphics, 1)) { if (ch >= 0xc2 && ch <= 0xf4 && (i + 1 == len || ((unsigned char)buf[i + 1] & 0xc0) == 0x80)) { if (!t->insert_mode && !t->lr_margin_mode && try_text_run(t, buf, len, &i)) continue; } else { - if (TERM_EXPECT(t->last_char != 0x200D && !grapheme_is_handle(t->last_char), 1)) + if (TERM_EXPECT(t->last_char != 0x200D && !term_grapheme_is_handle(t->last_char), 1)) sc_print_cell(t, 0xFFFD, 1, cur_flags); else emit_decoded(t, 0xFFFD, cur_flags); continue; } uint32_t cp; - int cp_len = utf8_decode_ground(&t->utf8_state, &t->utf8_codep, buf + i, len - i, &cp); + int cp_len = term_utf8_decode_ground(&t->utf8_state, &t->utf8_codep, buf + i, len - i, &cp); if (cp) emit_decoded(t, cp, cur_flags); i += (size_t)(cp_len - 1); @@ -328,7 +329,7 @@ static void term_feed_inner(struct term *t, const char *buf, size_t len, unsigne } continue; case 0x1b: - if (t->utf8_state == UTF8_ACCEPT && i + 1 < len && + if (t->utf8_state == TERM_UTF8_ACCEPT && i + 1 < len && buf[i + 1] == '[' && !t->insert_mode && !t->lr_margin_mode && !t->graphics) { int fast = term_try_fast_csi(t, buf + i + 2, len - i - 2); @@ -366,13 +367,16 @@ static void term_feed_inner(struct term *t, const char *buf, size_t len, unsigne } } -void term_feed(struct term *t, const char *buf, size_t len, unsigned flags) +bool term_feed(struct term *t, const char *buf, size_t len, unsigned flags) { - if (!t || !buf || len == 0) - return; + if (!t || (!buf && len > 0) || (flags & ~TERM_FEED_KNOWN)) + return false; + if (len == 0) + return true; term_write_begin(t); term_feed_inner(t, buf, len, flags); term_write_end(t); + return true; } void term_text_repeat(struct term *t, int count) diff --git a/src/text_run.c b/src/text_run.c index 028b160..c48b73e 100644 --- a/src/text_run.c +++ b/src/text_run.c @@ -19,7 +19,7 @@ static int width_run_count; void term_text_tables_init(void) { for (uint32_t cp = 0; cp < 65536; cp++) { - int w = unicode_width(cp); + int w = term_unicode_width(cp); unicode_fast_class[cp] = (w == 1 || w == 2) && !cluster_contextual(cp) ? (uint8_t)w : 0; unicode_width_bmp[cp] = (int8_t)w; @@ -92,7 +92,7 @@ static TERM_MAYBE_UNUSED bool width_cache_extend(struct width_cache *wc, const u int term_text_class(uint32_t cp) { assert(term_tables_ready()); - return TERM_EXPECT(cp < 65536, 1) ? (int)unicode_width_bmp[cp] : unicode_width(cp); + return TERM_EXPECT(cp < 65536, 1) ? (int)unicode_width_bmp[cp] : term_unicode_width(cp); } #if defined(TERM_KERNEL_AVX512) @@ -189,10 +189,10 @@ static inline bool classify_3byte_block(__m512i in, struct width_ranges *ranges, _mm512_loadu_si512(utf8_3byte_bits)) & UTF8_3BYTE_SPAN, 0)) return false; *cps = utf8_3byte_decode(_mm512_permutexvar_epi8(_mm512_loadu_si512(utf8_3byte_shuffle), in)); - if (_mm512_cmplt_epu32_mask(*cps, _mm512_set1_epi32((int)UTF8_3BYTE_MIN)) || + if (_mm512_cmplt_epu32_mask(*cps, _mm512_set1_epi32((int)TERM_UTF8_3BYTE_MIN)) || _mm512_cmple_epu32_mask( - _mm512_sub_epi32(*cps, _mm512_set1_epi32((int)UTF8_SURROGATE_LO)), - _mm512_set1_epi32((int)(UTF8_SURROGATE_HI - UTF8_SURROGATE_LO)))) + _mm512_sub_epi32(*cps, _mm512_set1_epi32((int)TERM_UTF8_SURROGATE_LO)), + _mm512_set1_epi32((int)(TERM_UTF8_SURROGATE_HI - TERM_UTF8_SURROGATE_LO)))) return false; __mmask16 covered = width_ranges_cover(ranges, *cps); if (TERM_EXPECT(covered != 0xFFFF || wc->klass == 0, 0)) { @@ -289,7 +289,7 @@ text_3byte_blocks(uint8_t * restrict dst, const char * restrict buf, int len, in struct text_run_bytes term_text_run_bytes(uint8_t *restrict dst, const char *restrict buf, int len, int max_cols, uint32_t last, struct width_cache *restrict wc) { - if (TERM_EXPECT(last == 0x200D || grapheme_is_handle(last), 0)) + if (TERM_EXPECT(last == 0x200D || term_grapheme_is_handle(last), 0)) return (struct text_run_bytes){ .last = last, .narrow = true }; /* Unlike term_text_run(), this fills up to the edge and up to the end of * buf: the cells path writes those characters one by one with the same @@ -335,7 +335,7 @@ struct text_run_bytes term_text_run_bytes(uint8_t *restrict dst, const char *res } #endif uint32_t cp; - int bytes = utf8_decode_one(buf + i, (size_t)(len - i), &cp); + int bytes = term_utf8_decode_one(buf + i, (size_t)(len - i), &cp); if (!bytes) break; int w = bytes < 4 ? unicode_fast_class[cp] : term_text_class(cp); @@ -493,9 +493,9 @@ static inline int decode_3byte_quads(uint64_t **dst_io, const char *buf, int len _mm_and_si128(_mm_srli_epi32(packed, 2), _mm_set1_epi32(0x0FC0))), _mm_and_si128(_mm_srli_epi32(packed, 4), _mm_set1_epi32(0xF000))); __m128i bad = _mm_or_si128( - _mm_cmplt_epi32(cps, _mm_set1_epi32((int)UTF8_3BYTE_MIN)), + _mm_cmplt_epi32(cps, _mm_set1_epi32((int)TERM_UTF8_3BYTE_MIN)), _mm_cmpeq_epi32(_mm_and_si128(cps, _mm_set1_epi32(0xF800)), - _mm_set1_epi32((int)UTF8_SURROGATE_LO))); + _mm_set1_epi32((int)TERM_UTF8_SURROGATE_LO))); if (_mm_movemask_epi8(bad)) break; alignas(16) uint32_t cp[4]; @@ -530,7 +530,7 @@ text_run_body(struct cell *restrict row, const char *restrict buf, int len, int { int i = 0; int cx = 0; - if (TERM_EXPECT(last == 0x200D || grapheme_is_handle(last), 0)) + if (TERM_EXPECT(last == 0x200D || term_grapheme_is_handle(last), 0)) return (struct text_run_result){0, 0, last}; const struct cell_bases bases = cell_bases_make(fg, bg, flags); @@ -593,14 +593,14 @@ text_run_body(struct cell *restrict row, const char *restrict buf, int len, int } #endif uint32_t cp; - int bytes = utf8_decode_one(buf + i, (size_t)(len - i), &cp); + int bytes = term_utf8_decode_one(buf + i, (size_t)(len - i), &cp); if (!bytes) break; int w = bytes < 4 ? unicode_fast_class[cp] : term_text_class(cp); if (w <= 0 || (bytes == 4 && can_extend_cluster(last, cp, w))) break; #if defined(TERM_KERNEL_AVX512) if (isa == TERM_ISA_AVX512 && bytes == 3 && w == 2 && i + 6 <= len && cx + 4 <= max_cols) { uint32_t next_cp; - if (utf8_decode_one(buf + i + 3, (size_t)(len - i - 3), &next_cp) == 3 && + if (term_utf8_decode_one(buf + i + 3, (size_t)(len - i - 3), &next_cp) == 3 && unicode_fast_class[next_cp] == 2) { store_wide_pair(dst, fg, bg, flags, cp, next_cp); dst += 8; diff --git a/src/text_run.h b/src/text_run.h index fac638f..3ff432b 100644 --- a/src/text_run.h +++ b/src/text_run.h @@ -53,25 +53,25 @@ static inline bool joins_cluster(uint32_t cp, int klass) { if (cp < 0x0300) return false; - if (klass == 0 || grapheme_emoji_modifier(cp)) + if (klass == 0 || term_grapheme_emoji_modifier(cp)) return true; - return klass == 1 && unicode_spacing_mark(cp); + return klass == 1 && term_unicode_spacing_mark(cp); } static inline bool can_extend_cluster(uint32_t last_char, uint32_t cp, int klass) { if (klass == 0) return true; - if (last_char == 0x200D || grapheme_is_handle(last_char)) return true; - if (cp - 0x1F1E6u <= 0x19u && grapheme_regional_indicator(cp)) + if (last_char == 0x200D || term_grapheme_is_handle(last_char)) return true; + if (cp - 0x1F1E6u <= 0x19u && term_grapheme_regional_indicator(cp)) return true; return joins_cluster(cp, klass); } static inline bool cluster_contextual(uint32_t cp) { - return unicode_spacing_mark(cp) || grapheme_emoji_modifier(cp) || - (cp - 0x1F1E6u <= 0x19u && grapheme_regional_indicator(cp)); + return term_unicode_spacing_mark(cp) || term_grapheme_emoji_modifier(cp) || + (cp - 0x1F1E6u <= 0x19u && term_grapheme_regional_indicator(cp)); } static inline bool cluster_joins(const uint32_t *cps, int len, @@ -80,10 +80,10 @@ static inline bool cluster_joins(const uint32_t *cps, int len, if (joins_cluster(cp, klass)) return true; if (cps[len - 1] == 0x200D) - return unicode_extended_pictographic(cp); + return term_unicode_extended_pictographic(cp); return len == 1 && cp - 0x1F1E6u <= 0x19u && - grapheme_regional_indicator(cp) && - grapheme_regional_indicator(cps[0]); + term_grapheme_regional_indicator(cp) && + term_grapheme_regional_indicator(cps[0]); } #endif |
