summaryrefslogtreecommitdiff
path: root/src/text.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/text.c')
-rw-r--r--src/text.c350
1 files changed, 350 insertions, 0 deletions
diff --git a/src/text.c b/src/text.c
new file mode 100644
index 0000000..9f73bd3
--- /dev/null
+++ b/src/text.c
@@ -0,0 +1,350 @@
+#include "compat.h"
+#include "core.h"
+#include "kernels.h"
+#include "text_run.h"
+#include "screen.h"
+#include "escape.h"
+#include "palette.h"
+#include "unicode/grapheme.h"
+#include "unicode/unicode.h"
+#include "text.h"
+#include "unicode/utf8.h"
+#include <stdio.h>
+#include <string.h>
+
+static void dispatch_c1_control(struct term *t, uint32_t cp)
+{
+ static const char c1_map[32] = {
+ [4] = 'D', [5] = 'E', [8] = 'H', [13] = 'M',
+ [16] = 'P', [27] = '[', [28] = '\\', [29] = ']' };
+ char ch = c1_map[cp - 0x80];
+ if (!ch) return;
+ t->state = ST_ESC;
+ term_escape_byte(t, (unsigned char)ch);
+}
+
+static inline void emit_codepoint(struct term *t, uint32_t cp, int klass)
+{
+ static const uint32_t dec_graphics[] = {
+ 0x00a0,
+ 0x25c6,0x2592,0x2409,0x240c,0x240d,0x240a,0x00b0,0x00b1,
+ 0x2424,0x240b,0x2518,0x2510,0x250c,0x2514,0x253c,0x23ba,
+ 0x23bb,0x2500,0x23bc,0x23bd,0x251c,0x2524,0x2534,0x252c,
+ 0x2502,0x2264,0x2265,0x03c0,0x2260,0x00a3,0x00b7 };
+ if (cp >= 0x80 && cp <= 0x9f) {
+ dispatch_c1_control(t, cp);
+ return;
+ }
+ if (t->graphics && cp >= '_' && cp <= '~') {
+ cp = dec_graphics[cp - '_'];
+ klass = -1;
+ }
+ term_sc_print_glyph(t, cp, klass, term_current_flags(t));
+}
+
+static inline void emit_decoded(struct term *t, uint32_t cp)
+{
+ int klass = term_text_class(cp);
+ if (!term_sc_extend_cluster(t, cp, klass))
+ emit_codepoint(t, cp, klass);
+}
+
+/* A byte that cannot continue a UTF-8 sequence ends it: the unfinished
+ * 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))
+ return;
+ t->utf8_state = UTF8_ACCEPT;
+ emit_decoded(t, 0xFFFD);
+}
+
+void term_text_ground(struct term *t, unsigned char ch)
+{
+ if (ch >= 0x20 && ch < 0x7f && t->utf8_state == UTF8_ACCEPT) {
+ emit_codepoint(t, ch, 1);
+ return;
+ }
+
+ if (ch < 0x20 || ch == 0x7f)
+ term_text_utf8_interrupt(t);
+
+ if (TERM_EXPECT(ch == 0x1b, 0)) {
+ term_escape_begin(t);
+ } else if (ch == '\n' || ch == 0x0b || ch == 0x0c) {
+ /* VT and FF act as LF (xterm). */
+ if (term_lnm(t)) sc_cr(t);
+ sc_linefeed(t, 1);
+ } else if (ch == '\r') {
+ sc_cr(t);
+ } else if (ch == '\b') {
+ sc_bs(t);
+ } else if (ch == '\t') {
+ term_sc_tab(t, 1);
+ } else if (ch == 0x07) {
+ if (t->cb.bell)
+ t->cb.bell(t->cb.ud);
+ } else if (ch == 0x0e) {
+ t->charset_active = 1;
+ t->graphics = t->charsets[1] == '0';
+ } else if (ch == 0x0f) {
+ t->charset_active = 0;
+ 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) {
+ emit_decoded(t, t->utf8_codep);
+ } else if (st == UTF8_REJECT) {
+ t->utf8_state = UTF8_ACCEPT;
+ emit_decoded(t, 0xFFFD);
+ /* The byte that ended an unfinished sequence starts anew. */
+ if (before != UTF8_ACCEPT)
+ term_text_ground(t, ch);
+ }
+ }
+}
+
+static inline int decode_ground_utf8(struct term *t, const char *buf, size_t len,
+ uint32_t *cp)
+{
+ return utf8_decode_ground(&t->utf8_state, &t->utf8_codep, buf, len, cp);
+}
+
+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->scroll_top == 0 && t->scroll_bottom == t->rows - 1 &&
+ t->cursor_y == t->rows - 1 &&
+ !t->alternate && t->sb_capacity > 0 &&
+ using_default_colors(t);
+}
+
+static inline bool try_text_run(struct term *t, const char *buf, size_t len,
+ size_t *idx)
+{
+ size_t i = *idx;
+ 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))
+ return false;
+ if ((unsigned char)buf[i] >= 0x80) {
+ uint32_t cp;
+ int bytes = utf8_decode_one(buf + i, len - i, &cp);
+ if (!bytes || term_text_class(cp) <= 0)
+ return false;
+ }
+ if (t->cursor_x != 0 &&
+ t->cursor_x != term_row(t, t->cursor_y)->len)
+ return false;
+ uint32_t fg = t->inverse ? t->cur_bg : t->cur_fg;
+ uint32_t bg = t->inverse ? t->cur_fg : t->cur_bg;
+ uint32_t flags = term_flags_pad(t);
+ bool wrote = false;
+ do {
+ struct srow *text = t->insert_mode || t->lr_margin_mode ? NULL : text_append_row(t);
+ if (text) {
+ int x = t->cursor_x, avail = t->cols - x;
+ struct text_run_bytes r = term_text_run_bytes(text->text.text + text->text.text_len,
+ buf + i, (int)(len - i), avail,
+ t->last_char, &t->text_width);
+ if (!r.bytes)
+ break;
+ wrote = true;
+ text_commit(text, r.out, r.chars, r.cols,
+ (uint8_t)((r.narrow ? HLINE_NARROW : 0) | (r.wide ? HLINE_HAS_WIDE : 0)),
+ fg, bg, flags);
+ t->last_char = r.last;
+ sc_text_advance(t, x, r.cols, avail);
+ i += (size_t)r.bytes;
+ } else {
+ struct span span = sc_span_begin(t, t->cols);
+ if (span.avail < 2)
+ break;
+ struct text_run_result r = term_text_run(span.cells, buf + i, (int)(len - i),
+ span.avail, fg, bg, flags, t->last_char,
+ &t->text_width);
+ if (!r.bytes)
+ break;
+ wrote = true;
+ sc_span_end(t, &span, r.cols, 0, r.last);
+ i += (size_t)r.bytes;
+ }
+ bool newline = false;
+ if (i < len && buf[i] == '\r') {
+ sc_cr(t);
+ i++;
+ if (i < len && buf[i] == '\n') {
+ sc_linefeed(t, 1);
+ i++;
+ newline = true;
+ }
+ } else if (i < len && buf[i] == '\n') {
+ if (term_lnm(t)) sc_cr(t);
+ sc_linefeed(t, 1);
+ i++;
+ newline = true;
+ }
+ if (!newline || i + 4 > len || t->pending_wrap ||
+ t->last_char == 0x200D || grapheme_is_handle(t->last_char) ||
+ (t->cursor_x != 0 &&
+ t->cursor_x != term_row(t, t->cursor_y)->len))
+ break;
+ } while (true);
+ if (wrote) *idx = i - 1;
+ return wrote;
+}
+
+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;
+
+ for (size_t i = 0; i < len; i++) {
+ while (t->state != ST_GROUND) {
+ if (t->state == ST_CSI)
+ i += term_escape_csi(t, buf + i, len - i);
+ else if (t->state == ST_OSC || t->state == ST_DCS || t->state == ST_IGNORE)
+ i += term_escape_str(t, buf + i, len - i);
+ else
+ term_escape_byte(t, (unsigned char)buf[i++]);
+ if (i == len) return;
+ }
+
+ unsigned char ch = (unsigned char)buf[i];
+ if ((ch < 0x20 || ch == 0x7f) && t->state == ST_GROUND)
+ term_text_utf8_interrupt(t);
+
+ if (TERM_EXPECT(ch < 0x20 && t->state == ST_GROUND &&
+ ch != 0x1b && ch != '\n' && ch != '\r' && ch != '\b' && ch != '\t' &&
+ ch != 0x0e && ch != 0x0f && ch != 0x07 && ch != 0x0b &&
+ ch != 0x0c, 0)) {
+ continue;
+ }
+
+ if (TERM_EXPECT(ch == 0x1b && t->state == ST_GROUND &&
+ t->utf8_state == UTF8_ACCEPT && i + 1 < len &&
+ buf[i + 1] == '[' && !t->insert_mode &&
+ !t->lr_margin_mode && !t->graphics, 0)) {
+ int fast = term_try_fast_csi(t, buf + i + 2, len - i - 2);
+ if (fast > 0) {
+ i += 1 + (size_t)fast;
+ continue;
+ }
+ t->state = ST_CSI;
+ csi_parser_reset(&t->csi_parser);
+ i += 1 + term_escape_csi(t, buf + i + 2, len - i - 2);
+ continue;
+ }
+
+ if (TERM_EXPECT(t->state == ST_GROUND && ch == '\n', 0)) {
+ size_t end = i + 1 + scan_byte_run(buf + i + 1, len - i - 1, '\n');
+ if (term_lnm(t)) sc_cr(t);
+ sc_linefeed(t, end - i);
+ i = end - 1;
+ continue;
+ }
+
+ if (TERM_EXPECT(t->state == ST_GROUND && ch == '\r' &&
+ i + 1 < len && buf[i + 1] == '\n', 0)) {
+ size_t end = i + 2 + scan_crlf_pairs(buf + i + 2, len - i - 2);
+ sc_cr(t);
+ sc_linefeed(t, (end - i) / 2);
+ i = end - 1;
+ continue;
+ }
+
+ 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)) {
+ int avail = t->pending_wrap ? t->cols : t->cols - t->cursor_x;
+ int rem = (int)(len - i);
+ if (rem > avail) rem = avail;
+ int run = 1;
+ if (rem > 1 && (unsigned char)buf[i + 1] >= 0x20 &&
+ (unsigned char)buf[i + 1] < 0x7f)
+ run += scan_ascii_run(buf + i + 1, rem - 1);
+ if (TERM_EXPECT(can_stream_bottom(t), 1) &&
+ (run == t->cols ||
+ (i + (size_t)run < len &&
+ (buf[i + run] == '\r' || buf[i + run] == '\n')))) {
+ struct srow *row = term_row(t, t->cursor_y);
+ if (t->pending_wrap ||
+ (t->cursor_x == 0 && row && row->len == 0)) {
+ size_t done = term_sc_bulk_ascii_lines(t, buf + i, len - i);
+ if (done > 0) {
+ i += done - 1;
+ continue;
+ }
+ }
+ }
+ 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) &&
+ term_text_class(next_cp) > 0 &&
+ try_text_run(t, buf, len, &i))
+ continue;
+ }
+ sc_print_ascii(t, buf + i, run);
+ i += (size_t)run;
+ if (i < len && buf[i] == '\r') {
+ sc_cr(t);
+ i++;
+ if (i < len && buf[i] == '\n') {
+ sc_linefeed(t, 1);
+ i++;
+ }
+ } else if (i < len && buf[i] == '\n') {
+ if (term_lnm(t)) sc_cr(t);
+ sc_linefeed(t, 1);
+ i++;
+ }
+ i--;
+ continue;
+ }
+
+ if (TERM_EXPECT(ch == '\r' && t->state == ST_GROUND, 0)) {
+ sc_cr(t);
+ continue;
+ }
+
+ if (TERM_EXPECT(ch >= 0x80 && t->state == ST_GROUND &&
+ t->utf8_state == UTF8_ACCEPT && !t->graphics, 1)) {
+ if (!t->insert_mode && !t->lr_margin_mode &&
+ try_text_run(t, buf, len, &i))
+ continue;
+ uint32_t cp;
+ int cp_len = decode_ground_utf8(t, buf + i, len - i, &cp);
+ if (cp)
+ emit_decoded(t, cp);
+ i += (size_t)(cp_len - 1);
+ continue;
+ }
+
+ if (TERM_EXPECT(t->state == ST_GROUND, 1))
+ term_text_ground(t, ch);
+ else
+ term_escape_byte(t, ch);
+ }
+}
+
+void term_feed(struct term *t, const char *buf, size_t len, unsigned flags)
+{
+ if (!t || !buf || len == 0)
+ return;
+ term_write_begin(t);
+ term_feed_inner(t, buf, len, flags);
+ term_write_end(t);
+}
+
+void term_text_repeat(struct term *t, int count)
+{
+ int screen = t->cols * t->rows;
+ if (count > screen) count = screen;
+ for (int j = 0; j < count && t->last_char; j++)
+ term_sc_print_glyph(t, t->last_char, -1, term_current_flags(t));
+}