summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/compat.h75
-rw-r--r--src/core.c24
-rw-r--r--src/escape.c4
-rw-r--r--src/gstore.c28
-rw-r--r--src/hist.c14
-rw-r--r--src/input.c4
-rw-r--r--src/internal.h20
-rw-r--r--src/meson.build8
-rw-r--r--src/osc/misc.c2
-rw-r--r--src/screen.c47
-rw-r--r--src/selection.c10
-rw-r--r--src/snapshot.c26
-rw-r--r--src/term.c7
-rw-r--r--src/text.c52
-rw-r--r--src/text_run.c24
-rw-r--r--src/text_run.h18
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)
{
diff --git a/src/core.c b/src/core.c
index b352be1..dd6232a 100644
--- a/src/core.c
+++ b/src/core.c
@@ -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;
}
diff --git a/src/hist.c b/src/hist.c
index 344cdc8..fff0bfe 100644
--- a/src/hist.c
+++ b/src/hist.c
@@ -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;
}
diff --git a/src/term.c b/src/term.c
index 1b16670..5839da3 100644
--- a/src/term.c
+++ b/src/term.c
@@ -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)
diff --git a/src/text.c b/src/text.c
index 43c3ef5..17f3f4f 100644
--- a/src/text.c
+++ b/src/text.c
@@ -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